diff --git a/src/helpers/links.js b/src/helpers/links.js
index 007c9f791d1..b4223f9c59c 100644
--- a/src/helpers/links.js
+++ b/src/helpers/links.js
@@ -56,6 +56,9 @@ const domHref = function(node) {
if (ref.match(/^[a-zA-Z]*:/)) {
return ref
}
+ if (ref.startsWith('#')) {
+ return ref
+ }
const match = ref.match(/^([^?]*)\?fileId=(\d+)/)
if (match) {
const [, relPath, id] = match
@@ -82,9 +85,10 @@ const openLink = function(event, _attrs) {
const linkElement = event.target.closest('a')
const htmlHref = linkElement.href
const query = OC.parseQueryString(htmlHref)
- const fragment = OC.parseQueryString(htmlHref.split('#').pop())
- if (query.dir && fragment.relPath) {
- const filename = fragment.relPath.split('/').pop()
+ const fragment = htmlHref.split('#').pop()
+ const fragmentQuery = OC.parseQueryString(fragment)
+ if (query?.dir && fragmentQuery?.relPath) {
+ const filename = fragmentQuery.relPath.split('/').pop()
const path = `${query.dir}/${filename}`
document.title = `${filename} - ${OC.theme.title}`
if (window.location.pathname.match(/apps\/files\/$/)) {
@@ -95,7 +99,7 @@ const openLink = function(event, _attrs) {
OCA.Viewer.open({ path })
return
}
- if (query.fileId) {
+ if (query?.fileId) {
// open the direct file link
window.open(generateUrl(`/f/${query.fileId}`))
return
@@ -104,6 +108,13 @@ const openLink = function(event, _attrs) {
console.error('Invalid link', htmlHref)
return false
}
+ if (fragment) {
+ const el = document.getElementById(fragment)
+ if (el) {
+ el.scrollIntoView()
+ return
+ }
+ }
window.open(htmlHref)
return true
}
diff --git a/src/nodes/Heading/HeadingView.vue b/src/nodes/Heading/HeadingView.vue
new file mode 100644
index 00000000000..c2a728a3d1d
--- /dev/null
+++ b/src/nodes/Heading/HeadingView.vue
@@ -0,0 +1,112 @@
+
+
+
+
+ {{ linkSymbol }}
+
+
+
+
+
+
+
diff --git a/src/nodes/Heading/index.js b/src/nodes/Heading/index.js
index ce7e73e7862..63614da7ef1 100644
--- a/src/nodes/Heading/index.js
+++ b/src/nodes/Heading/index.js
@@ -1,8 +1,18 @@
import TipTapHeading from '@tiptap/extension-heading'
+import { VueNodeViewRenderer } from '@tiptap/vue-2'
import debounce from 'debounce'
+
+import HeadingView from './HeadingView.vue'
import { setHeadings, extractHeadings } from './extractor.js'
const Heading = TipTapHeading.extend({
+ addOptions() {
+ return {
+ ...this.parent?.(),
+ linkSymbol: '#',
+ }
+ },
+
addAttributes() {
return {
...this.parent(),
@@ -16,6 +26,11 @@ const Heading = TipTapHeading.extend({
},
}
},
+
+ addNodeView() {
+ return VueNodeViewRenderer(HeadingView)
+ },
+
addKeyboardShortcuts() {
return this.options.levels.reduce((items, level) => ({
...items,
diff --git a/src/plugins/link.js b/src/plugins/link.js
index 4002dc0dea5..0349c50d524 100644
--- a/src/plugins/link.js
+++ b/src/plugins/link.js
@@ -15,7 +15,6 @@ const clickHandler = ({ editor, type, onClick }) => {
console.debug(link)
return false
}
-
// We use custom onClick handler only for left clicks
if (event.button === 0 && !event.ctrlKey) {
event.stopPropagation()