Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion playwright/e2e/conflict.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,14 @@ test('conflict dialog is sticky when scrolling', async ({
const pushPromise = page.waitForRequest(/push/)
await editor.typeHeading('Long content\n')
await pushPromise
await setOffline()

for (let i = 1; i < 8; i++) {
await editor.typeHeading(`Section ${i}`)
await editor.type('\n\nLorem ipsum dolor sit amet.\n\n')
}

await setOffline()
await editor.type('unsaved changes')
await close()

await setOnline()
Expand Down
3 changes: 3 additions & 0 deletions src/EditorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ const createRichEditor = ({
relativePath,
isEmbedded = false,
mentionSearch = undefined,
openLink = undefined,
}: {
extensions?: Extension[]
connection?: Connection
relativePath?: string
isEmbedded?: boolean
mentionSearch?: (query: string) => Promise<Record<string, string>>
openLink?: (href: string) => void
} = {}) => {
return new Editor({
editorProps,
Expand All @@ -60,6 +62,7 @@ const createRichEditor = ({
relativePath,
isEmbedded,
mentionSearch,
openLink,
}),
FocusTrap,
...extensions,
Expand Down
12 changes: 0 additions & 12 deletions src/components/Editor.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { openLink } from '../helpers/links.js'
import { logger } from '../helpers/logger.js'

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

export const useIsMobileMixin = {
Expand Down Expand Up @@ -55,13 +53,3 @@ export const useMentionHook = {
},
},
}
export const useOpenLinkHandler = {
inject: {
$openLinkHandler: {
from: OPEN_LINK_HANDLER,
default: {
openLink,
},
},
},
}
5 changes: 4 additions & 1 deletion src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import Autofocus from '../extensions/Autofocus.js'

import { provideEditor } from '../composables/useEditor.ts'
import { provideEditorFlags } from '../composables/useEditorFlags.ts'
import { useOpenLinkHandler } from '../composables/useOpenLinkHandler.ts'
import {
ATTACHMENT_RESOLVER,
HOOK_MENTION_SEARCH,
Expand Down Expand Up @@ -269,14 +270,16 @@ export default defineComponent({
Collaboration.configure({ document: ydoc }),
CollaborationCaret.configure({ provider: { awareness } }),
]
const mentionSearch = inject(HOOK_MENTION_SEARCH)
const mentionSearch = inject(HOOK_MENTION_SEARCH, undefined)
const { openLinkHandler } = useOpenLinkHandler()
const editor = isRichEditor
? createRichEditor({
connection,
relativePath: props.relativePath,
extensions,
isEmbedded: props.isEmbedded,
mentionSearch,
openLink: openLinkHandler.openLink,
})
: createPlainEditor({ language, extensions })
provideEditor(editor)
Expand Down
3 changes: 3 additions & 0 deletions src/components/Editor/MarkdownContentEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { editorFlagsKey } from '../../composables/useEditorFlags.ts'
import { provideEditorHeadings } from '../../composables/useEditorHeadings.ts'
import { useEditorMethods } from '../../composables/useEditorMethods.ts'
import { provideEditorWidth } from '../../composables/useEditorWidth.ts'
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
import { FocusTrap, RichText } from '../../extensions/index.js'
import { createMarkdownSerializer } from '../../extensions/Markdown.js'
import AttachmentResolver from '../../services/AttachmentResolver.js'
Expand Down Expand Up @@ -82,9 +83,11 @@ export default {
emits: ['update:content'],

setup(props) {
const { openLinkHandler } = useOpenLinkHandler()
const extensions = [
RichText.configure({
extensions: [UndoRedo],
openLink: openLinkHandler.openLink,
}),
FocusTrap,
]
Expand Down
8 changes: 7 additions & 1 deletion src/components/Editor/PreviewOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue'
import DotsVerticalIcon from 'vue-material-design-icons/DotsVertical.vue'
import OpenIcon from 'vue-material-design-icons/OpenInNew.vue'
import DeleteOutlineIcon from 'vue-material-design-icons/TrashCanOutline.vue'
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin.js'

export default {
Expand Down Expand Up @@ -104,6 +105,11 @@ export default {
},
},

setup() {
const { openLinkHandler } = useOpenLinkHandler()
return { openLinkHandler }
},

data() {
return {
open: false,
Expand All @@ -126,7 +132,7 @@ export default {
},
openLink() {
if (!this.href) return
window.open(this.href, '_blank').focus()
this.openLinkHandler.openLink(this.href)
},
async copyLink() {
await this.copyToClipboard(this.href)
Expand Down
11 changes: 7 additions & 4 deletions src/components/Link/LinkBubbleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import PencilOutlineIcon from 'vue-material-design-icons/PencilOutline.vue'

import { useOpenLinkHandler } from '../Editor.provider.ts'
import { useOpenLinkHandler } from '../../composables/useOpenLinkHandler.ts'
import PreviewOptions from '../Editor/PreviewOptions.vue'

const PROTOCOLS_WITH_PREVIEW = ['http:', 'https:']
Expand All @@ -116,8 +116,6 @@ export default {
PencilOutlineIcon,
},

mixins: [useOpenLinkHandler],

props: {
editor: {
type: Object,
Expand All @@ -129,6 +127,11 @@ export default {
},
},

setup() {
const { openLinkHandler } = useOpenLinkHandler()
return { openLinkHandler }
},

data() {
return {
isEditable: false,
Expand Down Expand Up @@ -191,7 +194,7 @@ export default {
},

openLink(href) {
this.$openLinkHandler.openLink(href)
this.openLinkHandler.openLink(href)
},

onReferenceListLoaded() {
Expand Down
17 changes: 17 additions & 0 deletions src/composables/useOpenLinkHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { inject } from 'vue'
import { openLink } from '../helpers/links.js'

export const OPEN_LINK_HANDLER = Symbol('editor:open-link-handler')

/**
* Inject provided link handler
*/
export function useOpenLinkHandler() {
const openLinkHandler = inject(OPEN_LINK_HANDLER, { openLink })
return { openLinkHandler }
}
2 changes: 1 addition & 1 deletion src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
HOOK_MENTION_INSERT,
HOOK_MENTION_SEARCH,
HOOK_MENUBAR_LINK_CUSTOM_ACTION,
OPEN_LINK_HANDLER,
} from './components/Editor.provider.ts'
import { ACTION_ATTACHMENT_PROMPT } from './components/Editor/MediaHandler.provider.js'
import { OPEN_LINK_HANDLER } from './composables/useOpenLinkHandler.ts'
import { encodeAttachmentFilename } from './helpers/attachmentFilename.ts'
import { openLink } from './helpers/links.js'
// eslint-disable-next-line import/no-unresolved, n/no-missing-import
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/LinkBubble.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { Extension } from '@tiptap/core'
import { hideLinkBubble, linkBubble } from '../plugins/links.js'
import { hideLinkBubble, linkBubble } from '../plugins/links.ts'

const LinkBubble = Extension.create({
name: 'linkViewBubble',
Expand Down
2 changes: 2 additions & 0 deletions src/extensions/RichText.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default Extension.create({
relativePath: null,
isEmbedded: false,
mentionSearch: undefined,
openLink: undefined,
}
},

Expand Down Expand Up @@ -131,6 +132,7 @@ export default Extension.create({
openOnClick: true,
shouldAutoLink: (href) => /^https?:\/\//.test(href),
relativePath: this.options.relativePath,
openLink: this.options.openLink,
}),
LinkBubble,
this.options.editing
Expand Down
14 changes: 1 addition & 13 deletions src/helpers/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,7 @@ const isLinkToSelfWithHash = function (href) {
*/
const openLink = function (href) {
const linkUrl = new URL(href, window.location.href)
// Consider rerouting links to Collectives if already inside Collectives app
const collectivesUrlBase = '/apps/collectives'
if (
window.OCA.Collectives?.vueRouter
&& linkUrl.pathname.toString().startsWith(generateUrl(collectivesUrlBase))
) {
const collectivesUrl = linkUrl.href.substring(
linkUrl.href.indexOf(collectivesUrlBase) + collectivesUrlBase.length,
)
window.OCA.Collectives.vueRouter.push(collectivesUrl)
return
}
window.open(linkUrl, '_blank')
window.open(linkUrl.href, '_blank')
}

export { domHref, isLinkToSelfWithHash, openLink, parseHref }
6 changes: 4 additions & 2 deletions src/marks/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Mark, Node } from '@tiptap/pm/model'
import type { MarkdownSerializerState } from 'prosemirror-markdown'
import { defaultMarkdownSerializer } from 'prosemirror-markdown'
import { domHref, parseHref } from '../helpers/links.js'
import { linkClicking } from '../plugins/links.js'
import { linkClicking } from '../plugins/links'

const PROTOCOLS_TO_LINK_TO = ['http:', 'https:', 'mailto:', 'tel:']

Expand All @@ -33,6 +33,7 @@ const extractHrefFromMarkdownLink = (match: ExtendedRegExpMatchArray) => {

export interface RelativePathLinkOptions extends LinkOptions {
relativePath?: string
openLink?: (href: string) => void
}

const parentDefaults: LinkOptions = {
Expand Down Expand Up @@ -98,6 +99,7 @@ const Link = TipTapLink.extend<RelativePathLinkOptions>({
return {
...this.parent?.(),
relativePath: undefined,
openLink: undefined,
...parentDefaults,
}
},
Expand Down Expand Up @@ -247,7 +249,7 @@ const Link = TipTapLink.extend<RelativePathLinkOptions>({
.filter((plugin) => !plugin.props.handleClick)

// Add our own click handler plugin
return [...plugins, linkClicking()]
return [...plugins, linkClicking(this.options.openLink)]
},

// @ts-expect-error - toMarkdown is a custom field not part of the official Tiptap API
Expand Down
Loading
Loading