Skip to content

Commit 4dd9978

Browse files
authored
Merge pull request #8571 from nextcloud/fix/link_handler
fix(links): allow to pass custom link handler into editor and use it
2 parents 2e5d89a + 0276fe2 commit 4dd9978

15 files changed

Lines changed: 96 additions & 65 deletions

File tree

playwright/e2e/conflict.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,14 @@ test('conflict dialog is sticky when scrolling', async ({
335335
const pushPromise = page.waitForRequest(/push/)
336336
await editor.typeHeading('Long content\n')
337337
await pushPromise
338-
await setOffline()
339338

340339
for (let i = 1; i < 8; i++) {
341340
await editor.typeHeading(`Section ${i}`)
342341
await editor.type('\n\nLorem ipsum dolor sit amet.\n\n')
343342
}
343+
344+
await setOffline()
345+
await editor.type('unsaved changes')
344346
await close()
345347

346348
await setOnline()

src/EditorFactory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ const createRichEditor = ({
4545
relativePath,
4646
isEmbedded = false,
4747
mentionSearch = undefined,
48+
openLink = undefined,
4849
}: {
4950
extensions?: Extension[]
5051
connection?: Connection
5152
relativePath?: string
5253
isEmbedded?: boolean
5354
mentionSearch?: (query: string) => Promise<Record<string, string>>
55+
openLink?: (href: string) => void
5456
} = {}) => {
5557
return new Editor({
5658
editorProps,
@@ -60,6 +62,7 @@ const createRichEditor = ({
6062
relativePath,
6163
isEmbedded,
6264
mentionSearch,
65+
openLink,
6366
}),
6467
FocusTrap,
6568
...extensions,

src/components/Editor.provider.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { openLink } from '../helpers/links.js'
76
import { logger } from '../helpers/logger.js'
87

98
export const ATTACHMENT_RESOLVER = Symbol('attachment:resolver')
109
export const IS_MOBILE = Symbol('editor:is-mobile')
1110
export const EDITOR_UPLOAD = Symbol('editor:upload')
1211
export const HOOK_MENTION_SEARCH = Symbol('hook:mention-search')
1312
export const HOOK_MENTION_INSERT = Symbol('hook:mention-insert')
14-
export const OPEN_LINK_HANDLER = Symbol('editor:open-link-handler')
1513
export const HOOK_MENUBAR_LINK_CUSTOM_ACTION = Symbol('menubar:link-custom-action')
1614

1715
export const useIsMobileMixin = {
@@ -55,13 +53,3 @@ export const useMentionHook = {
5553
},
5654
},
5755
}
58-
export const useOpenLinkHandler = {
59-
inject: {
60-
$openLinkHandler: {
61-
from: OPEN_LINK_HANDLER,
62-
default: {
63-
openLink,
64-
},
65-
},
66-
},
67-
}

src/components/Editor.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ import Autofocus from '../extensions/Autofocus.js'
9696
9797
import { provideEditor } from '../composables/useEditor.ts'
9898
import { provideEditorFlags } from '../composables/useEditorFlags.ts'
99+
import { useOpenLinkHandler } from '../composables/useOpenLinkHandler.ts'
99100
import {
100101
ATTACHMENT_RESOLVER,
101102
HOOK_MENTION_SEARCH,
@@ -269,14 +270,16 @@ export default defineComponent({
269270
Collaboration.configure({ document: ydoc }),
270271
CollaborationCaret.configure({ provider: { awareness } }),
271272
]
272-
const mentionSearch = inject(HOOK_MENTION_SEARCH)
273+
const mentionSearch = inject(HOOK_MENTION_SEARCH, undefined)
274+
const { openLinkHandler } = useOpenLinkHandler()
273275
const editor = isRichEditor
274276
? createRichEditor({
275277
connection,
276278
relativePath: props.relativePath,
277279
extensions,
278280
isEmbedded: props.isEmbedded,
279281
mentionSearch,
282+
openLink: openLinkHandler.openLink,
280283
})
281284
: createPlainEditor({ language, extensions })
282285
provideEditor(editor)

src/components/Editor/MarkdownContentEditor.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { editorFlagsKey } from '../../composables/useEditorFlags.ts'
3131
import { provideEditorHeadings } from '../../composables/useEditorHeadings.ts'
3232
import { useEditorMethods } from '../../composables/useEditorMethods.ts'
3333
import { provideEditorWidth } from '../../composables/useEditorWidth.ts'
34+
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
3435
import { FocusTrap, RichText } from '../../extensions/index.js'
3536
import { createMarkdownSerializer } from '../../extensions/Markdown.js'
3637
import AttachmentResolver from '../../services/AttachmentResolver.js'
@@ -82,9 +83,11 @@ export default {
8283
emits: ['update:content'],
8384
8485
setup(props) {
86+
const { openLinkHandler } = useOpenLinkHandler()
8587
const extensions = [
8688
RichText.configure({
8789
extensions: [UndoRedo],
90+
openLink: openLinkHandler.openLink,
8891
}),
8992
FocusTrap,
9093
]

src/components/Editor/PreviewOptions.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue'
7272
import DotsVerticalIcon from 'vue-material-design-icons/DotsVertical.vue'
7373
import OpenIcon from 'vue-material-design-icons/OpenInNew.vue'
7474
import DeleteOutlineIcon from 'vue-material-design-icons/TrashCanOutline.vue'
75+
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
7576
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin.js'
7677
7778
export default {
@@ -104,6 +105,11 @@ export default {
104105
},
105106
},
106107
108+
setup() {
109+
const { openLinkHandler } = useOpenLinkHandler()
110+
return { openLinkHandler }
111+
},
112+
107113
data() {
108114
return {
109115
open: false,
@@ -126,7 +132,7 @@ export default {
126132
},
127133
openLink() {
128134
if (!this.href) return
129-
window.open(this.href, '_blank').focus()
135+
this.openLinkHandler.openLink(this.href)
130136
},
131137
async copyLink() {
132138
await this.copyToClipboard(this.href)

src/components/Link/LinkBubbleView.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
9797
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
9898
import PencilOutlineIcon from 'vue-material-design-icons/PencilOutline.vue'
9999
100-
import { useOpenLinkHandler } from '../Editor.provider.ts'
100+
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
101101
import PreviewOptions from '../Editor/PreviewOptions.vue'
102102
103103
const PROTOCOLS_WITH_PREVIEW = ['http:', 'https:']
@@ -116,8 +116,6 @@ export default {
116116
PencilOutlineIcon,
117117
},
118118
119-
mixins: [useOpenLinkHandler],
120-
121119
props: {
122120
editor: {
123121
type: Object,
@@ -129,6 +127,11 @@ export default {
129127
},
130128
},
131129
130+
setup() {
131+
const { openLinkHandler } = useOpenLinkHandler()
132+
return { openLinkHandler }
133+
},
134+
132135
data() {
133136
return {
134137
isEditable: false,
@@ -191,7 +194,7 @@ export default {
191194
},
192195
193196
openLink(href) {
194-
this.$openLinkHandler.openLink(href)
197+
this.openLinkHandler.openLink(href)
195198
},
196199
197200
onReferenceListLoaded() {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { inject } from 'vue'
7+
import { openLink } from '../helpers/links.js'
8+
9+
export const OPEN_LINK_HANDLER = Symbol('editor:open-link-handler')
10+
11+
/**
12+
* Inject provided link handler
13+
*/
14+
export function useOpenLinkHandler() {
15+
const openLinkHandler = inject(OPEN_LINK_HANDLER, { openLink })
16+
return { openLinkHandler }
17+
}

src/editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {
1111
HOOK_MENTION_INSERT,
1212
HOOK_MENTION_SEARCH,
1313
HOOK_MENUBAR_LINK_CUSTOM_ACTION,
14-
OPEN_LINK_HANDLER,
1514
} from './components/Editor.provider.ts'
1615
import { ACTION_ATTACHMENT_PROMPT } from './components/Editor/MediaHandler.provider.js'
16+
import { OPEN_LINK_HANDLER } from './composables/useOpenLinkHandler.ts'
1717
import { encodeAttachmentFilename } from './helpers/attachmentFilename.ts'
1818
import { openLink } from './helpers/links.js'
1919
// eslint-disable-next-line import/no-unresolved, n/no-missing-import

src/extensions/LinkBubble.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Extension } from '@tiptap/core'
7-
import { hideLinkBubble, linkBubble } from '../plugins/links.js'
7+
import { hideLinkBubble, linkBubble } from '../plugins/links.ts'
88

99
const LinkBubble = Extension.create({
1010
name: 'linkViewBubble',

0 commit comments

Comments
 (0)