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
path: root/src/nodes
diff options
context:
space:
mode:
authorFerdinand Thiessen <rpm@fthiessen.de>2022-05-30 22:01:21 +0300
committerVinicius Reis <vinicius.reis@nextcloud.com>2022-09-01 15:34:41 +0300
commitddc73ff28f76077c73b91b10f6671ffd383e8bf6 (patch)
tree976f261484c6005f887ded59b0f9ed1314a7f45f /src/nodes
parent9973d1fb5230d8c9fb46394a14fbdcc90d339a3a (diff)
Add visible and clickable links for the page anchors
Handle inter-page links properly and use a heading view for displaying links and link anchors. Implements #2173 Also fix link handling, to scroll to selected header when clicking on an anchor link (`#some-id`). Signed-off-by: Ferdinand Thiessen <rpm@fthiessen.de>
Diffstat (limited to 'src/nodes')
-rw-r--r--src/nodes/Heading/HeadingView.vue112
-rw-r--r--src/nodes/Heading/index.js15
2 files changed, 127 insertions, 0 deletions
diff --git a/src/nodes/Heading/HeadingView.vue b/src/nodes/Heading/HeadingView.vue
new file mode 100644
index 000000000..c2a728a3d
--- /dev/null
+++ b/src/nodes/Heading/HeadingView.vue
@@ -0,0 +1,112 @@
+<!--
+ - @copyright Copyright (c) 2022
+ -
+ - @license AGPL-3.0-or-later
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <NodeViewWrapper ref="content"
+ :as="domElement"
+ v-bind="node.attrs"
+ tabindex="-1">
+ <a aria-hidden="true"
+ class="anchor-link"
+ :href="href"
+ :title="t('text', 'Link to this section')"
+ @click.stop="click">{{ linkSymbol }}</a>
+ <NodeViewContent />
+ </NodeViewWrapper>
+</template>
+
+<script>
+import Vue from 'vue'
+import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'
+import { useEditorMixin } from '../../components/Editor.provider.js'
+
+export default Vue.extend({
+ name: 'HeadingView',
+ components: {
+ NodeViewWrapper,
+ NodeViewContent,
+ },
+ mixins: [useEditorMixin],
+ props: {
+ node: {
+ type: Object,
+ required: true,
+ },
+ extension: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ href() {
+ return `#${this.node.attrs.id}`
+ },
+ domElement() {
+ const hasLevel = this.extension.options.levels.includes(this.node.attrs.level)
+ const level = hasLevel ? this.node.attrs.level : this.extension.options.levels[0]
+ return `h${level}`
+ },
+ linkSymbol() {
+ return this.extension.options.linkSymbol
+ },
+ t: () => window.t,
+ },
+
+ methods: {
+ click() {
+ this.$refs.content.$el.scrollIntoView()
+ window.location.hash = this.href
+ },
+ },
+})
+</script>
+
+<style scoped lang="scss">
+div.ProseMirror {
+ /* Anchor links */
+ h1, h2, h3, h4, h5, h6 {
+ .anchor-link {
+ opacity: 0;
+ padding: 0;
+ left: -18px;
+ font-size: max(1em, 16px);
+ position: absolute;
+ text-decoration: none;
+ transition-duration: .15s;
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(.4,0,.2,1);
+ }
+
+ &:hover .anchor-link {
+ opacity: 0.45;
+ }
+ }
+
+ // Shrink clickable area of anchor permalinks while editing
+ &.ProseMirror-focused[contenteditable="true"] {
+ h1,h2,h3,h4,h5,h6 {
+ .anchor-link {
+ width: fit-content;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/nodes/Heading/index.js b/src/nodes/Heading/index.js
index ce7e73e78..63614da7e 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,