mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Refactor markdown editor and use it for milestone description editor (#32688)
Refactor markdown editor to clarify its "preview" behavior and remove jQuery code. Close #15045 --------- Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		| @@ -1,6 +1,5 @@ | ||||
| import '@github/markdown-toolbar-element'; | ||||
| import '@github/text-expander-element'; | ||||
| import $ from 'jquery'; | ||||
| import {attachTribute} from '../tribute.ts'; | ||||
| import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts'; | ||||
| import { | ||||
| @@ -23,6 +22,8 @@ import { | ||||
| } from './EditorMarkdown.ts'; | ||||
| import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts'; | ||||
| import {createTippy} from '../../modules/tippy.ts'; | ||||
| import {fomanticQuery} from '../../modules/fomantic/base.ts'; | ||||
| import type EasyMDE from 'easymde'; | ||||
|  | ||||
| let elementIdCounter = 0; | ||||
|  | ||||
| @@ -48,18 +49,23 @@ export function validateTextareaNonEmpty(textarea) { | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| type ComboMarkdownEditorOptions = { | ||||
|   editorHeights?: {minHeight?: string, height?: string, maxHeight?: string}, | ||||
|   easyMDEOptions?: EasyMDE.Options, | ||||
| }; | ||||
|  | ||||
| export class ComboMarkdownEditor { | ||||
|   static EventEditorContentChanged = EventEditorContentChanged; | ||||
|   static EventUploadStateChanged = EventUploadStateChanged; | ||||
|  | ||||
|   public container : HTMLElement; | ||||
|  | ||||
|   // TODO: use correct types to replace these "any" types | ||||
|   options: any; | ||||
|   options: ComboMarkdownEditorOptions; | ||||
|  | ||||
|   tabEditor: HTMLElement; | ||||
|   tabPreviewer: HTMLElement; | ||||
|  | ||||
|   supportEasyMDE: boolean; | ||||
|   easyMDE: any; | ||||
|   easyMDEToolbarActions: any; | ||||
|   easyMDEToolbarDefault: any; | ||||
| @@ -71,11 +77,12 @@ export class ComboMarkdownEditor { | ||||
|   dropzone: HTMLElement; | ||||
|   attachedDropzoneInst: any; | ||||
|  | ||||
|   previewMode: string; | ||||
|   previewUrl: string; | ||||
|   previewContext: string; | ||||
|   previewMode: string; | ||||
|  | ||||
|   constructor(container, options = {}) { | ||||
|   constructor(container, options:ComboMarkdownEditorOptions = {}) { | ||||
|     if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized'); | ||||
|     container._giteaComboMarkdownEditor = this; | ||||
|     this.options = options; | ||||
|     this.container = container; | ||||
| @@ -99,6 +106,10 @@ export class ComboMarkdownEditor { | ||||
|   } | ||||
|  | ||||
|   setupContainer() { | ||||
|     this.supportEasyMDE = this.container.getAttribute('data-support-easy-mde') === 'true'; | ||||
|     this.previewMode = this.container.getAttribute('data-content-mode'); | ||||
|     this.previewUrl = this.container.getAttribute('data-preview-url'); | ||||
|     this.previewContext = this.container.getAttribute('data-preview-context'); | ||||
|     initTextExpander(this.container.querySelector('text-expander')); | ||||
|   } | ||||
|  | ||||
| @@ -137,12 +148,14 @@ export class ComboMarkdownEditor { | ||||
|       monospaceButton.setAttribute('aria-checked', String(enabled)); | ||||
|     }); | ||||
|  | ||||
|     const easymdeButton = this.container.querySelector('.markdown-switch-easymde'); | ||||
|     easymdeButton.addEventListener('click', async (e) => { | ||||
|       e.preventDefault(); | ||||
|       this.userPreferredEditor = 'easymde'; | ||||
|       await this.switchToEasyMDE(); | ||||
|     }); | ||||
|     if (this.supportEasyMDE) { | ||||
|       const easymdeButton = this.container.querySelector('.markdown-switch-easymde'); | ||||
|       easymdeButton.addEventListener('click', async (e) => { | ||||
|         e.preventDefault(); | ||||
|         this.userPreferredEditor = 'easymde'; | ||||
|         await this.switchToEasyMDE(); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     this.initMarkdownButtonTableAdd(); | ||||
|  | ||||
| @@ -187,6 +200,7 @@ export class ComboMarkdownEditor { | ||||
|  | ||||
|   setupTab() { | ||||
|     const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item'); | ||||
|     if (!tabs.length) return; | ||||
|  | ||||
|     // Fomantic Tab requires the "data-tab" to be globally unique. | ||||
|     // So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic. | ||||
| @@ -207,11 +221,8 @@ export class ComboMarkdownEditor { | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     $(tabs).tab(); | ||||
|     fomanticQuery(tabs).tab(); | ||||
|  | ||||
|     this.previewUrl = this.tabPreviewer.getAttribute('data-preview-url'); | ||||
|     this.previewContext = this.tabPreviewer.getAttribute('data-preview-context'); | ||||
|     this.previewMode = this.options.previewMode ?? 'comment'; | ||||
|     this.tabPreviewer.addEventListener('click', async () => { | ||||
|       const formData = new FormData(); | ||||
|       formData.append('mode', this.previewMode); | ||||
| @@ -219,7 +230,7 @@ export class ComboMarkdownEditor { | ||||
|       formData.append('text', this.value()); | ||||
|       const response = await POST(this.previewUrl, {data: formData}); | ||||
|       const data = await response.text(); | ||||
|       renderPreviewPanelContent($(panelPreviewer), data); | ||||
|       renderPreviewPanelContent(panelPreviewer, data); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -284,7 +295,7 @@ export class ComboMarkdownEditor { | ||||
|   } | ||||
|  | ||||
|   async switchToUserPreference() { | ||||
|     if (this.userPreferredEditor === 'easymde') { | ||||
|     if (this.userPreferredEditor === 'easymde' && this.supportEasyMDE) { | ||||
|       await this.switchToEasyMDE(); | ||||
|     } else { | ||||
|       this.switchToTextarea(); | ||||
| @@ -304,7 +315,7 @@ export class ComboMarkdownEditor { | ||||
|     if (this.easyMDE) return; | ||||
|     // EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles. | ||||
|     const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde'); | ||||
|     const easyMDEOpt = { | ||||
|     const easyMDEOpt: EasyMDE.Options = { | ||||
|       autoDownloadFontAwesome: false, | ||||
|       element: this.textarea, | ||||
|       forceSync: true, | ||||
| @@ -384,19 +395,20 @@ export class ComboMarkdownEditor { | ||||
|   } | ||||
|  | ||||
|   get userPreferredEditor() { | ||||
|     return window.localStorage.getItem(`markdown-editor-${this.options.useScene ?? 'default'}`); | ||||
|     return window.localStorage.getItem(`markdown-editor-${this.previewMode ?? 'default'}`); | ||||
|   } | ||||
|   set userPreferredEditor(s) { | ||||
|     window.localStorage.setItem(`markdown-editor-${this.options.useScene ?? 'default'}`, s); | ||||
|     window.localStorage.setItem(`markdown-editor-${this.previewMode ?? 'default'}`, s); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function getComboMarkdownEditor(el) { | ||||
|   if (el instanceof $) el = el[0]; | ||||
|   return el?._giteaComboMarkdownEditor; | ||||
|   if (!el) return null; | ||||
|   if (el.length) el = el[0]; | ||||
|   return el._giteaComboMarkdownEditor; | ||||
| } | ||||
|  | ||||
| export async function initComboMarkdownEditor(container: HTMLElement, options = {}) { | ||||
| export async function initComboMarkdownEditor(container: HTMLElement, options:ComboMarkdownEditorOptions = {}) { | ||||
|   if (!container) { | ||||
|     throw new Error('initComboMarkdownEditor: container is null'); | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 wxiaoguang
					wxiaoguang