Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax <max@nextcloud.com>2022-04-11 17:11:37 +0300
committerMax <max@nextcloud.com>2022-06-07 20:41:57 +0300
commit5bf15b4dc0e1e683f08db3aff57f258443715ef5 (patch)
tree4e351ae05d015b444e41e1055813d22d38947d9a
parent0b7b0d61a0bbf7ef25bf8fe36af2f8d8718f507f (diff)
feature: emit clickLink from `ReadOnlyEditor`
The Prosemirror plugin with the `handleClick` handler only customizes the prosemirror handling of the click event. In read only mode we are not in a content-editable section. So clicking a link will cause the browser to open the url with a page reload. Allow overwriting this behavior by handling all link clicks via prosemirror. Set `onClick` option on the `Link` mark to customize the behavior. Emit a `click-link` event from `ReadOnlyEditor` with info about the event and the attributes of the link mark. Find the link that was clicked based on the clicked marks rather than the element in the event. This way we can get access to the attributes of the mark without relying on the selection or even changing it. Also add plugin key to link click handler Signed-off-by: Max <max@nextcloud.com>
-rw-r--r--src/components/ReadOnlyEditor.vue19
-rw-r--r--src/extensions/RichText.js6
-rw-r--r--src/helpers/links.js35
-rw-r--r--src/marks/Link.js38
-rw-r--r--src/plugins/link.js52
5 files changed, 93 insertions, 57 deletions
diff --git a/src/components/ReadOnlyEditor.vue b/src/components/ReadOnlyEditor.vue
index 02d2b8c1c..e35aa9896 100644
--- a/src/components/ReadOnlyEditor.vue
+++ b/src/components/ReadOnlyEditor.vue
@@ -80,11 +80,17 @@ export default {
this.editor.destroy()
},
methods: {
+
createRichEditor() {
return new Editor({
content: this.htmlContent,
extensions: [
- RichText.configure(this.richTextOptions),
+ RichText.configure({
+ ...this.richTextOptions,
+ link: {
+ onClick: (event, attrs) => this.$emit('click-link', event, attrs),
+ },
+ }),
...this.extensions,
],
})
@@ -97,6 +103,17 @@ export default {
})
},
+ /* Stop the browser from opening links.
+ * Clicks are handled inside the Link mark just like in edit mode.
+ */
+ preventOpeningLinks() {
+ this.$el.addEventListener('click', event => {
+ if (event.target.closest('a')) {
+ event.preventDefault()
+ }
+ })
+ },
+
updateContent() {
this.editor.commands.setContent(this.htmlContent)
},
diff --git a/src/extensions/RichText.js b/src/extensions/RichText.js
index d9a9ea0e7..ce8931371 100644
--- a/src/extensions/RichText.js
+++ b/src/extensions/RichText.js
@@ -58,6 +58,7 @@ export default Extension.create({
addOptions() {
return {
currentDirectory: undefined,
+ link: {},
}
},
@@ -94,7 +95,10 @@ export default Extension.create({
Dropcursor,
]
if (this.options.link !== false) {
- extensions.push(Link.configure({ openOnClick: true }))
+ extensions.push(Link.configure({
+ ...this.options.link,
+ openOnClick: true,
+ }))
}
return extensions
},
diff --git a/src/helpers/links.js b/src/helpers/links.js
index 18f556b5c..81df51444 100644
--- a/src/helpers/links.js
+++ b/src/helpers/links.js
@@ -21,6 +21,7 @@
*/
import { generateUrl } from '@nextcloud/router'
+import markdownit from './../markdownit/index.js'
const absolutePath = function(base, rel) {
if (!rel) {
@@ -77,7 +78,41 @@ const parseHref = function(dom) {
return ref
}
+const openLink = function(event, _attrs) {
+ const linkElement = event.target.closest('a')
+ event.stopPropagation()
+ const htmlHref = linkElement.href
+ if (event.button === 0 && !event.ctrlKey && htmlHref.startsWith(window.location.origin)) {
+ const query = OC.parseQueryString(htmlHref)
+ const fragment = OC.parseQueryString(htmlHref.split('#').pop())
+ if (query.dir && fragment.relPath) {
+ const filename = fragment.relPath.split('/').pop()
+ const path = `${query.dir}/${filename}`
+ document.title = `${filename} - ${OC.theme.title}`
+ if (window.location.pathname.match(/apps\/files\/$/)) {
+ // The files app still lacks a popState handler
+ // to allow for using the back button
+ // OC.Util.History.pushState('', htmlHref)
+ }
+ OCA.Viewer.open({ path })
+ return
+ }
+ if (query.fileId) {
+ // open the direct file link
+ window.open(generateUrl(`/f/${query.fileId}`))
+ return
+ }
+ }
+ if (!markdownit.validateLink(htmlHref)) {
+ console.error('Invalid link', htmlHref)
+ return false
+ }
+ window.open(htmlHref)
+ return true
+}
+
export {
domHref,
parseHref,
+ openLink,
}
diff --git a/src/marks/Link.js b/src/marks/Link.js
index 20cd22b9f..f6e1d7c4a 100644
--- a/src/marks/Link.js
+++ b/src/marks/Link.js
@@ -21,20 +21,29 @@
*/
import TipTapLink from '@tiptap/extension-link'
-import { domHref, parseHref } from './../helpers/links.js'
+import { domHref, parseHref, openLink } from './../helpers/links.js'
import { clickHandler } from '../plugins/link.js'
const Link = TipTapLink.extend({
- attrs: {
- href: {
- default: null,
- },
+ addOptions() {
+ return {
+ ...this.parent?.(),
+ onClick: openLink,
+ }
+ },
+
+ addAttributes() {
+ return {
+ href: {
+ default: null,
+ },
+ }
},
inclusive: false,
- parseDOM: [
+ parseHTML: [
{
tag: 'a[href]',
getAttrs: dom => ({
@@ -43,10 +52,10 @@ const Link = TipTapLink.extend({
},
],
- toDOM: node => ['a', {
- ...node.attrs,
- href: domHref(node),
- title: node.attrs.href,
+ renderHTML: ({ mark, HTMLAttributes }) => ['a', {
+ ...mark.attrs,
+ href: domHref(mark),
+ title: mark.attrs.href,
rel: 'noopener noreferrer nofollow',
}, 0],
@@ -62,7 +71,14 @@ const Link = TipTapLink.extend({
}
// add custom click handler
- return [...plugins, clickHandler({ editor: this.editor, type: this.type })]
+ return [
+ ...plugins,
+ clickHandler({
+ editor: this.editor,
+ type: this.type,
+ onClick: this.options.onClick,
+ }),
+ ]
},
})
diff --git a/src/plugins/link.js b/src/plugins/link.js
index 5f352851e..1a656362e 100644
--- a/src/plugins/link.js
+++ b/src/plugins/link.js
@@ -1,52 +1,16 @@
-import { generateUrl } from '@nextcloud/router'
-import { Plugin } from 'prosemirror-state'
-import markdownit from './../markdownit/index.js'
+import { Plugin, PluginKey } from 'prosemirror-state'
-const clickHandler = ({ editor }) => {
+const clickHandler = ({ editor, type, onClick }) => {
return new Plugin({
props: {
+ key: new PluginKey('textLink'),
handleClick: (view, pos, event) => {
- const linkElement = event.target.parentElement instanceof HTMLAnchorElement
- ? event.target.parentElement
- : event.target
-
- const isLink = linkElement && linkElement instanceof HTMLAnchorElement
-
- const htmlHref = linkElement?.href
-
- // is handleable link
- if (htmlHref && isLink) {
- event.stopPropagation()
-
- if (event.button === 0 && !event.ctrlKey && htmlHref.startsWith(window.location.origin)) {
- const query = OC.parseQueryString(htmlHref)
- const fragment = OC.parseQueryString(htmlHref.split('#').pop())
- if (query.dir && fragment.relPath) {
- const filename = fragment.relPath.split('/').pop()
- const path = `${query.dir}/${filename}`
- document.title = `${filename} - ${OC.theme.title}`
- if (window.location.pathname.match(/apps\/files\/$/)) {
- // The files app still lacks a popState handler
- // to allow for using the back button
- // OC.Util.History.pushState('', htmlHref)
- }
- OCA.Viewer.open({ path })
- return
- }
- if (query.fileId) {
- // open the direct file link
- window.open(generateUrl(`/f/${query.fileId}`))
- return
- }
- }
-
- if (!markdownit.validateLink(htmlHref)) {
- console.error('Invalid link', htmlHref)
- return
- }
-
- window.open(htmlHref)
+ const attrs = editor.getAttributes(type)
+ const link = event.target.closest('a')
+ if (link && attrs.href && onClick) {
+ return onClick(event, attrs)
}
+ return false
},
},
})