mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Add some handy markdown editor features (#32400)
There were some missing features from EasyMDE: 1. H1 - H3 style 2. Auto add task list 3. Insert a table And added some tests
This commit is contained in:
		| @@ -209,6 +209,10 @@ buttons.link.tooltip = Add a link | |||||||
| buttons.list.unordered.tooltip = Add a bullet list | buttons.list.unordered.tooltip = Add a bullet list | ||||||
| buttons.list.ordered.tooltip = Add a numbered list | buttons.list.ordered.tooltip = Add a numbered list | ||||||
| buttons.list.task.tooltip = Add a list of tasks | buttons.list.task.tooltip = Add a list of tasks | ||||||
|  | buttons.table.add.tooltip = Add a table | ||||||
|  | buttons.table.add.insert = Add | ||||||
|  | buttons.table.rows = Rows | ||||||
|  | buttons.table.cols = Columns | ||||||
| buttons.mention.tooltip = Mention a user or team | buttons.mention.tooltip = Mention a user or team | ||||||
| buttons.ref.tooltip = Reference an issue or pull request | buttons.ref.tooltip = Reference an issue or pull request | ||||||
| buttons.switch_to_legacy.tooltip = Use the legacy editor instead | buttons.switch_to_legacy.tooltip = Use the legacy editor instead | ||||||
|   | |||||||
| @@ -21,7 +21,11 @@ Template Attributes: | |||||||
| 	<div class="ui tab active" data-tab-panel="markdown-writer"> | 	<div class="ui tab active" data-tab-panel="markdown-writer"> | ||||||
| 		<markdown-toolbar> | 		<markdown-toolbar> | ||||||
| 			<div class="markdown-toolbar-group"> | 			<div class="markdown-toolbar-group"> | ||||||
| 				<md-header class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> | 				<md-header class="markdown-toolbar-button" level="1" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> | ||||||
|  | 				<md-header class="markdown-toolbar-button" level="2" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> | ||||||
|  | 				<md-header class="markdown-toolbar-button" level="3" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.heading.tooltip"}}">{{svg "octicon-heading"}}</md-header> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="markdown-toolbar-group"> | ||||||
| 				<md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}">{{svg "octicon-bold"}}</md-bold> | 				<md-bold class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.bold.tooltip"}}">{{svg "octicon-bold"}}</md-bold> | ||||||
| 				<md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}">{{svg "octicon-italic"}}</md-italic> | 				<md-italic class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.italic.tooltip"}}">{{svg "octicon-italic"}}</md-italic> | ||||||
| 			</div> | 			</div> | ||||||
| @@ -34,6 +38,7 @@ Template Attributes: | |||||||
| 				<md-unordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.unordered.tooltip"}}">{{svg "octicon-list-unordered"}}</md-unordered-list> | 				<md-unordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.unordered.tooltip"}}">{{svg "octicon-list-unordered"}}</md-unordered-list> | ||||||
| 				<md-ordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.ordered.tooltip"}}">{{svg "octicon-list-ordered"}}</md-ordered-list> | 				<md-ordered-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.ordered.tooltip"}}">{{svg "octicon-list-ordered"}}</md-ordered-list> | ||||||
| 				<md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list> | 				<md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list> | ||||||
|  | 				<button class="markdown-toolbar-button markdown-button-table-add" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.table.add.tooltip"}}">{{svg "octicon-table"}}</button> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="markdown-toolbar-group"> | 			<div class="markdown-toolbar-group"> | ||||||
| 				<md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention> | 				<md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention> | ||||||
| @@ -56,4 +61,12 @@ Template Attributes: | |||||||
| 	<div class="ui tab markup" data-tab-panel="markdown-previewer"> | 	<div class="ui tab markup" data-tab-panel="markdown-previewer"> | ||||||
| 		{{ctx.Locale.Tr "loading"}} | 		{{ctx.Locale.Tr "loading"}} | ||||||
| 	</div> | 	</div> | ||||||
|  | 	<div class="markdown-add-table-panel tippy-target"> | ||||||
|  | 		<div class="ui form tw-p-4 flex-text-block"> | ||||||
|  | 			<input type="number" name="rows" min="1" value="3" size="3" class="tw-w-24" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.table.rows"}}"> | ||||||
|  | 			x | ||||||
|  | 			<input type="number" name="cols" min="1" value="3" size="3" class="tw-w-24" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.table.cols"}}"> | ||||||
|  | 			<button class="ui button primary" type="button">{{ctx.Locale.Tr "editor.buttons.table.add.insert"}}</button> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -7,17 +7,25 @@ | |||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   padding-bottom: 10px; |   padding-bottom: 10px; | ||||||
|   gap: .5rem; |  | ||||||
|   flex-wrap: wrap; |   flex-wrap: wrap; | ||||||
| } | } | ||||||
|  |  | ||||||
| .combo-markdown-editor .markdown-toolbar-group { | .combo-markdown-editor .markdown-toolbar-group { | ||||||
|   display: flex; |   display: flex; | ||||||
|  |   border-left: 1px solid var(--color-secondary); | ||||||
|  |   padding: 0 0.5em; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .combo-markdown-editor .markdown-toolbar-group:first-child { | ||||||
|  |   border-left: 0; | ||||||
|  |   padding-left: 0; | ||||||
|  | } | ||||||
| .combo-markdown-editor .markdown-toolbar-group:last-child { | .combo-markdown-editor .markdown-toolbar-group:last-child { | ||||||
|   flex: 1; |   flex: 1; | ||||||
|   justify-content: flex-end; |   justify-content: flex-end; | ||||||
|  |   border-right: none; | ||||||
|  |   border-left: 0; | ||||||
|  |   padding-right: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| .combo-markdown-editor .markdown-toolbar-button { | .combo-markdown-editor .markdown-toolbar-button { | ||||||
| @@ -33,6 +41,24 @@ | |||||||
|   color: var(--color-primary); |   color: var(--color-primary); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .combo-markdown-editor md-header { | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  | .combo-markdown-editor md-header::after { | ||||||
|  |   font-size: 10px; | ||||||
|  |   position: absolute; | ||||||
|  |   top: 7px; | ||||||
|  | } | ||||||
|  | .combo-markdown-editor md-header[level="1"]::after { | ||||||
|  |   content: "1"; | ||||||
|  | } | ||||||
|  | .combo-markdown-editor md-header[level="2"]::after { | ||||||
|  |   content: "2"; | ||||||
|  | } | ||||||
|  | .combo-markdown-editor md-header[level="3"]::after { | ||||||
|  |   content: "3"; | ||||||
|  | } | ||||||
|  |  | ||||||
| .ui.form .combo-markdown-editor textarea.markdown-text-editor, | .ui.form .combo-markdown-editor textarea.markdown-text-editor, | ||||||
| .combo-markdown-editor textarea.markdown-text-editor { | .combo-markdown-editor textarea.markdown-text-editor { | ||||||
|   display: block; |   display: block; | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ | |||||||
|   padding: 0.5em 0 0; |   padding: 0.5em 0 0; | ||||||
|   border: none; |   border: none; | ||||||
|   border-top: none; |   border-top: none; | ||||||
|   line-height: 1.2; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .edit-content-zone .comment { | .edit-content-zone .comment { | ||||||
|   | |||||||
| @@ -15,8 +15,14 @@ import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts'; | |||||||
| import {initTextExpander} from './TextExpander.ts'; | import {initTextExpander} from './TextExpander.ts'; | ||||||
| import {showErrorToast} from '../../modules/toast.ts'; | import {showErrorToast} from '../../modules/toast.ts'; | ||||||
| import {POST} from '../../modules/fetch.ts'; | import {POST} from '../../modules/fetch.ts'; | ||||||
| import {EventEditorContentChanged, initTextareaMarkdown, triggerEditorContentChanged} from './EditorMarkdown.ts'; | import { | ||||||
|  |   EventEditorContentChanged, | ||||||
|  |   initTextareaMarkdown, | ||||||
|  |   textareaInsertText, | ||||||
|  |   triggerEditorContentChanged, | ||||||
|  | } from './EditorMarkdown.ts'; | ||||||
| import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts'; | import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts'; | ||||||
|  | import {createTippy} from '../../modules/tippy.ts'; | ||||||
|  |  | ||||||
| let elementIdCounter = 0; | let elementIdCounter = 0; | ||||||
|  |  | ||||||
| @@ -122,8 +128,7 @@ export class ComboMarkdownEditor { | |||||||
|     const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text'); |     const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text'); | ||||||
|     monospaceButton.setAttribute('data-tooltip-content', monospaceText); |     monospaceButton.setAttribute('data-tooltip-content', monospaceText); | ||||||
|     monospaceButton.setAttribute('aria-checked', String(monospaceEnabled)); |     monospaceButton.setAttribute('aria-checked', String(monospaceEnabled)); | ||||||
|  |     monospaceButton.addEventListener('click', (e) => { | ||||||
|     monospaceButton?.addEventListener('click', (e) => { |  | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true'; |       const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true'; | ||||||
|       localStorage.setItem('markdown-editor-monospace', String(enabled)); |       localStorage.setItem('markdown-editor-monospace', String(enabled)); | ||||||
| @@ -134,12 +139,14 @@ export class ComboMarkdownEditor { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     const easymdeButton = this.container.querySelector('.markdown-switch-easymde'); |     const easymdeButton = this.container.querySelector('.markdown-switch-easymde'); | ||||||
|     easymdeButton?.addEventListener('click', async (e) => { |     easymdeButton.addEventListener('click', async (e) => { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       this.userPreferredEditor = 'easymde'; |       this.userPreferredEditor = 'easymde'; | ||||||
|       await this.switchToEasyMDE(); |       await this.switchToEasyMDE(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     this.initMarkdownButtonTableAdd(); | ||||||
|  |  | ||||||
|     initTextareaMarkdown(this.textarea); |     initTextareaMarkdown(this.textarea); | ||||||
|     initTextareaEvents(this.textarea, this.dropzone); |     initTextareaEvents(this.textarea, this.dropzone); | ||||||
|   } |   } | ||||||
| @@ -219,6 +226,42 @@ export class ComboMarkdownEditor { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   generateMarkdownTable(rows: number, cols: number): string { | ||||||
|  |     const tableLines = []; | ||||||
|  |     tableLines.push( | ||||||
|  |       `| ${'Header '.repeat(cols).trim().split(' ').join(' | ')} |`, | ||||||
|  |       `| ${'--- '.repeat(cols).trim().split(' ').join(' | ')} |`, | ||||||
|  |     ); | ||||||
|  |     for (let i = 0; i < rows; i++) { | ||||||
|  |       tableLines.push(`| ${'Cell '.repeat(cols).trim().split(' ').join(' | ')} |`); | ||||||
|  |     } | ||||||
|  |     return tableLines.join('\n'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   initMarkdownButtonTableAdd() { | ||||||
|  |     const addTableButton = this.container.querySelector('.markdown-button-table-add'); | ||||||
|  |     const addTablePanel = this.container.querySelector('.markdown-add-table-panel'); | ||||||
|  |     // here the tippy can't attach to the button because the button already owns a tippy for tooltip | ||||||
|  |     const addTablePanelTippy = createTippy(addTablePanel, { | ||||||
|  |       content: addTablePanel, | ||||||
|  |       trigger: 'manual', | ||||||
|  |       placement: 'bottom', | ||||||
|  |       hideOnClick: true, | ||||||
|  |       interactive: true, | ||||||
|  |       getReferenceClientRect: () => addTableButton.getBoundingClientRect(), | ||||||
|  |     }); | ||||||
|  |     addTableButton.addEventListener('click', () => addTablePanelTippy.show()); | ||||||
|  |  | ||||||
|  |     addTablePanel.querySelector('.ui.button.primary').addEventListener('click', () => { | ||||||
|  |       let rows = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=rows]').value); | ||||||
|  |       let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]').value); | ||||||
|  |       rows = Math.max(1, Math.min(100, rows)); | ||||||
|  |       cols = Math.max(1, Math.min(100, cols)); | ||||||
|  |       textareaInsertText(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`); | ||||||
|  |       addTablePanelTippy.hide(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   switchTabToEditor() { |   switchTabToEditor() { | ||||||
|     this.tabEditor.click(); |     this.tabEditor.click(); | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								web_src/js/features/comp/EditorMarkdown.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web_src/js/features/comp/EditorMarkdown.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import {initTextareaMarkdown} from './EditorMarkdown.ts'; | ||||||
|  |  | ||||||
|  | test('EditorMarkdown', () => { | ||||||
|  |   const textarea = document.createElement('textarea'); | ||||||
|  |   initTextareaMarkdown(textarea); | ||||||
|  |  | ||||||
|  |   const testInput = (value, expected) => { | ||||||
|  |     textarea.value = value; | ||||||
|  |     textarea.setSelectionRange(value.length, value.length); | ||||||
|  |     const e = new KeyboardEvent('keydown', {key: 'Enter', cancelable: true}); | ||||||
|  |     textarea.dispatchEvent(e); | ||||||
|  |     if (!e.defaultPrevented) textarea.value += '\n'; | ||||||
|  |     expect(textarea.value).toEqual(expected); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   testInput('-', '-\n'); | ||||||
|  |   testInput('1.', '1.\n'); | ||||||
|  |  | ||||||
|  |   testInput('- ', ''); | ||||||
|  |   testInput('1. ', ''); | ||||||
|  |  | ||||||
|  |   testInput('- x', '- x\n- '); | ||||||
|  |   testInput('- [ ]', '- [ ]\n- '); | ||||||
|  |   testInput('- [ ] foo', '- [ ] foo\n- [ ] '); | ||||||
|  |   testInput('* [x] foo', '* [x] foo\n* [ ] '); | ||||||
|  |   testInput('1. [x] foo', '1. [x] foo\n1. [ ] '); | ||||||
|  | }); | ||||||
| @@ -4,6 +4,16 @@ export function triggerEditorContentChanged(target) { | |||||||
|   target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true})); |   target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true})); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function textareaInsertText(textarea, value) { | ||||||
|  |   const startPos = textarea.selectionStart; | ||||||
|  |   const endPos = textarea.selectionEnd; | ||||||
|  |   textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos); | ||||||
|  |   textarea.selectionStart = startPos; | ||||||
|  |   textarea.selectionEnd = startPos + value.length; | ||||||
|  |   textarea.focus(); | ||||||
|  |   triggerEditorContentChanged(textarea); | ||||||
|  | } | ||||||
|  |  | ||||||
| function handleIndentSelection(textarea, e) { | function handleIndentSelection(textarea, e) { | ||||||
|   const selStart = textarea.selectionStart; |   const selStart = textarea.selectionStart; | ||||||
|   const selEnd = textarea.selectionEnd; |   const selEnd = textarea.selectionEnd; | ||||||
| @@ -46,7 +56,7 @@ function handleIndentSelection(textarea, e) { | |||||||
|   triggerEditorContentChanged(textarea); |   triggerEditorContentChanged(textarea); | ||||||
| } | } | ||||||
|  |  | ||||||
| function handleNewline(textarea, e) { | function handleNewline(textarea: HTMLTextAreaElement, e: Event) { | ||||||
|   const selStart = textarea.selectionStart; |   const selStart = textarea.selectionStart; | ||||||
|   const selEnd = textarea.selectionEnd; |   const selEnd = textarea.selectionEnd; | ||||||
|   if (selEnd !== selStart) return; // do not process when there is a selection |   if (selEnd !== selStart) return; // do not process when there is a selection | ||||||
| @@ -66,9 +76,9 @@ function handleNewline(textarea, e) { | |||||||
|   const indention = /^\s*/.exec(line)[0]; |   const indention = /^\s*/.exec(line)[0]; | ||||||
|   line = line.slice(indention.length); |   line = line.slice(indention.length); | ||||||
|  |  | ||||||
|   // parse the prefixes: "1. ", "- ", "* ", "[ ] ", "[x] " |   // parse the prefixes: "1. ", "- ", "* ", there could also be " [ ] " or " [x] " for task lists | ||||||
|   // there must be a space after the prefix because none of "1.foo" / "-foo" is a list item |   // there must be a space after the prefix because none of "1.foo" / "-foo" is a list item | ||||||
|   const prefixMatch = /^([0-9]+\.|[-*]|\[ \]|\[x\])\s/.exec(line); |   const prefixMatch = /^([0-9]+\.|[-*])(\s\[([ x])\])?\s/.exec(line); | ||||||
|   let prefix = ''; |   let prefix = ''; | ||||||
|   if (prefixMatch) { |   if (prefixMatch) { | ||||||
|     prefix = prefixMatch[0]; |     prefix = prefixMatch[0]; | ||||||
| @@ -85,8 +95,9 @@ function handleNewline(textarea, e) { | |||||||
|   } else { |   } else { | ||||||
|     // start a new line with the same indention and prefix |     // start a new line with the same indention and prefix | ||||||
|     let newPrefix = prefix; |     let newPrefix = prefix; | ||||||
|     if (newPrefix === '[x]') newPrefix = '[ ]'; |     // a simple approach, otherwise it needs to parse the lines after the current line | ||||||
|     if (/^\d+\./.test(newPrefix)) newPrefix = `1. `; // a simple approach, otherwise it needs to parse the lines after the current line |     if (/^\d+\./.test(prefix)) newPrefix = `1. ${newPrefix.slice(newPrefix.indexOf('.') + 2)}`; | ||||||
|  |     newPrefix = newPrefix.replace('[x]', '[ ]'); | ||||||
|     const newLine = `\n${indention}${newPrefix}`; |     const newLine = `\n${indention}${newPrefix}`; | ||||||
|     textarea.value = value.slice(0, selStart) + newLine + value.slice(selEnd); |     textarea.value = value.slice(0, selStart) + newLine + value.slice(selEnd); | ||||||
|     textarea.setSelectionRange(selStart + newLine.length, selStart + newLine.length); |     textarea.setSelectionRange(selStart + newLine.length, selStart + newLine.length); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import {imageInfo} from '../../utils/image.ts'; | import {imageInfo} from '../../utils/image.ts'; | ||||||
| import {replaceTextareaSelection} from '../../utils/dom.ts'; | import {replaceTextareaSelection} from '../../utils/dom.ts'; | ||||||
| import {isUrl} from '../../utils/url.ts'; | import {isUrl} from '../../utils/url.ts'; | ||||||
| import {triggerEditorContentChanged} from './EditorMarkdown.ts'; | import {textareaInsertText, triggerEditorContentChanged} from './EditorMarkdown.ts'; | ||||||
| import { | import { | ||||||
|   DropzoneCustomEventRemovedFile, |   DropzoneCustomEventRemovedFile, | ||||||
|   DropzoneCustomEventUploadDone, |   DropzoneCustomEventUploadDone, | ||||||
| @@ -41,14 +41,7 @@ class TextareaEditor { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   insertPlaceholder(value) { |   insertPlaceholder(value) { | ||||||
|     const editor = this.editor; |     textareaInsertText(this.editor, value); | ||||||
|     const startPos = editor.selectionStart; |  | ||||||
|     const endPos = editor.selectionEnd; |  | ||||||
|     editor.value = editor.value.substring(0, startPos) + value + editor.value.substring(endPos); |  | ||||||
|     editor.selectionStart = startPos; |  | ||||||
|     editor.selectionEnd = startPos + value.length; |  | ||||||
|     editor.focus(); |  | ||||||
|     triggerEditorContentChanged(editor); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   replacePlaceholder(oldVal, newVal) { |   replacePlaceholder(oldVal, newVal) { | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ type TippyOpts = { | |||||||
| const visibleInstances = new Set<Instance>(); | const visibleInstances = new Set<Instance>(); | ||||||
| const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; | const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; | ||||||
|  |  | ||||||
| export function createTippy(target: Element, opts: TippyOpts = {}) { | export function createTippy(target: Element, opts: TippyOpts = {}): Instance { | ||||||
|   // the callback functions should be destructured from opts, |   // the callback functions should be destructured from opts, | ||||||
|   // because we should use our own wrapper functions to handle them, do not let the user override them |   // because we should use our own wrapper functions to handle them, do not let the user override them | ||||||
|   const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; |   const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user