Skip to content

Commit

Permalink
feature: emit clickLink from ReadOnlyEditor
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
max-nextcloud committed Jun 7, 2022
1 parent 0b7b0d6 commit 5bf15b4
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 57 deletions.
19 changes: 18 additions & 1 deletion src/components/ReadOnlyEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
})
Expand All @@ -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)
},
Expand Down
6 changes: 5 additions & 1 deletion src/extensions/RichText.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default Extension.create({
addOptions() {
return {
currentDirectory: undefined,
link: {},
}
},

Expand Down Expand Up @@ -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
},
Expand Down
35 changes: 35 additions & 0 deletions src/helpers/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/

import { generateUrl } from '@nextcloud/router'
import markdownit from './../markdownit/index.js'

const absolutePath = function(base, rel) {
if (!rel) {
Expand Down Expand Up @@ -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,
}
38 changes: 27 additions & 11 deletions src/marks/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand All @@ -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],

Expand All @@ -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,
}),
]
},
})

Expand Down
52 changes: 8 additions & 44 deletions src/plugins/link.js
Original file line number Diff line number Diff line change
@@ -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
},
},
})
Expand Down

0 comments on commit 5bf15b4

Please sign in to comment.