diff --git a/src/components/input/editor/EditorToolbar.vue b/src/components/input/editor/EditorToolbar.vue index 9415c3b67..dd8d7e8c2 100644 --- a/src/components/input/editor/EditorToolbar.vue +++ b/src/components/input/editor/EditorToolbar.vue @@ -336,6 +336,7 @@ import {ref} from 'vue' import {Editor} from '@tiptap/vue-3' import BaseButton from '@/components/base/BaseButton.vue' +import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor' const { editor = null, @@ -353,29 +354,8 @@ function openImagePicker() { document.getElementById('tiptap__image-upload').click() } -function setLink() { - const previousUrl = editor.getAttributes('link').href - const url = window.prompt('URL', previousUrl) - - // cancelled - if (url === null) { - return - } - - // empty - if (url === '') { - editor.chain().focus().extendMarkRange('link').unsetLink().run() - - return - } - - // update link - editor - .chain() - .focus() - .extendMarkRange('link') - .setLink({href: url, target: '_blank'}) - .run() +function setLink(event) { + setLinkInEditor(event.target.getBoundingClientRect(), editor) } diff --git a/src/components/input/editor/TipTap.vue b/src/components/input/editor/TipTap.vue index f50e30314..f9448bfb4 100644 --- a/src/components/input/editor/TipTap.vue +++ b/src/components/input/editor/TipTap.vue @@ -174,6 +174,8 @@ import {Placeholder} from '@tiptap/extension-placeholder' import {eventToHotkeyString} from '@github/hotkey' import {mergeAttributes} from '@tiptap/core' import {isEditorContentEmpty} from '@/helpers/editorContentEmpty' +import inputPrompt from '@/helpers/inputPrompt' +import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor' const tiptapInstanceRef = ref(null) @@ -320,7 +322,7 @@ const editor = useEditor({ addKeyboardShortcuts() { return { 'Mod-Enter': () => { - if(contentHasChanged.value) { + if (contentHasChanged.value) { bubbleSave() } }, @@ -470,7 +472,7 @@ function uploadAndInsertFiles(files: File[] | FileList) { }) } -function addImage() { +async function addImage(event) { if (typeof uploadCallback !== 'undefined') { const files = uploadInputRef.value?.files @@ -484,7 +486,7 @@ function addImage() { return } - const url = window.prompt('URL') + const url = await inputPrompt(event.target.getBoundingClientRect()) if (url) { editor.value?.chain().focus().setImage({src: url}).run() @@ -492,34 +494,8 @@ function addImage() { } } -function setLink() { - const previousUrl = editor.value?.getAttributes('link').href - const url = window.prompt('URL', previousUrl) - - // cancelled - if (url === null) { - return - } - - // empty - if (url === '') { - editor.value - ?.chain() - .focus() - .extendMarkRange('link') - .unsetLink() - .run() - - return - } - - // update link - editor.value - ?.chain() - .focus() - .extendMarkRange('link') - .setLink({href: url, target: '_blank'}) - .run() +function setLink(event) { + setLinkInEditor(event.target.getBoundingClientRect(), editor.value) } onMounted(async () => { @@ -573,6 +549,7 @@ function setFocusToEditor(event) { event.target.contentEditable === 'true') { return } + event.preventDefault() if (!isEditing.value && isEditEnabled) { diff --git a/src/components/input/editor/setLinkInEditor.ts b/src/components/input/editor/setLinkInEditor.ts new file mode 100644 index 000000000..a25e515f4 --- /dev/null +++ b/src/components/input/editor/setLinkInEditor.ts @@ -0,0 +1,26 @@ +import inputPrompt from '@/helpers/inputPrompt' + +export async function setLinkInEditor(pos, editor) { + const previousUrl = editor?.getAttributes('link').href || '' + const url = await inputPrompt(pos, previousUrl) + + // empty + if (url === '') { + editor + ?.chain() + .focus() + .extendMarkRange('link') + .unsetLink() + .run() + + return + } + + // update link + editor + ?.chain() + .focus() + .extendMarkRange('link') + .setLink({href: url, target: '_blank'}) + .run() +} \ No newline at end of file diff --git a/src/helpers/inputPrompt.ts b/src/helpers/inputPrompt.ts new file mode 100644 index 000000000..7ba0fc54c --- /dev/null +++ b/src/helpers/inputPrompt.ts @@ -0,0 +1,39 @@ +import {createRandomID} from '@/helpers/randomId' +import tippy from 'tippy.js' +import {nextTick} from 'vue' +import {eventToHotkeyString} from '@github/hotkey' + +export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Promise { + return new Promise((resolve) => { + const id = 'link-input-' + createRandomID() + + const linkPopup = tippy('body', { + getReferenceClientRect: () => pos, + appendTo: () => document.body, + content: `
`, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'top-start', + allowHTML: true, + }) + + linkPopup[0].show() + + nextTick(() => document.getElementById(id)?.focus()) + + document.getElementById(id)?.addEventListener('keydown', event => { + const hotkeyString = eventToHotkeyString(event) + if (hotkeyString !== 'Enter') { + return + } + + const url = event.target.value + + resolve(url) + + linkPopup[0].hide() + }) + + }) +} \ No newline at end of file