Skip to content

Commit

Permalink
fix: handle opening links even in read only mode
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.

Signed-off-by: Max <[email protected]>
  • Loading branch information
max-nextcloud committed Apr 12, 2022
1 parent e15258a commit 8c6cc0c
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 44 deletions.
14 changes: 14 additions & 0 deletions package/components/ReadOnlyEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export default {
? this.createRichEditor()
: this.createPlainEditor()
this.editor.setOptions({ editable: false })
this.$nextTick(() => {
this.preventOpeningLinks()
})
},
beforeDestroy() {
this.editor.destroy()
Expand Down Expand Up @@ -90,6 +93,17 @@ export default {
extensions: [PlainText],
})
},

/* 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()
}
})
},
},
}
</script>
Expand Down
37 changes: 37 additions & 0 deletions package/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'

const absolutePath = function(base, rel) {
if (!rel) {
Expand Down Expand Up @@ -77,7 +78,43 @@ const parseHref = function(dom) {
return ref
}

const openLink = function(event, _attrs) {
const linkElement = event.target.parentElement instanceof HTMLAnchorElement
? event.target.parentElement
: event.target
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,
}
50 changes: 6 additions & 44 deletions package/marks/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@
*
*/

import { generateUrl } from '@nextcloud/router'
import TipTapLink from '@tiptap/extension-link'
import { Plugin, PluginKey } from 'prosemirror-state'
import { domHref, parseHref } from './../helpers/links'
import markdownit from './../markdownit'
import { domHref, parseHref, openLink } from './../helpers/links'

const Link = TipTapLink.extend({

addOptions() {
return {
...this.parent?.(),
onClick: undefined,
onClick: openLink,
}
},

Expand Down Expand Up @@ -71,47 +69,11 @@ const Link = TipTapLink.extend({
key: new PluginKey('textLink'),
handleClick: (_view, _pos, event) => {
const attrs = this.editor.getAttributes('link')
if (this.options.onClick) {
this.options.onClick(event, attrs)
return
}
const isLink = event.target instanceof HTMLAnchorElement
|| event.target.parentElement instanceof HTMLAnchorElement
if (attrs.href && isLink) {
const linkElement = event.target.parentElement instanceof HTMLAnchorElement
? event.target.parentElement
: event.target
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
}

window.open(htmlHref)
const link = event.target.closest('a')
if (link && attrs.href && this.options.onClick) {
return this.options.onClick(event, attrs)
}
return false
},
},
}),
Expand Down

0 comments on commit 8c6cc0c

Please sign in to comment.