-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathpreviewOptions.js
More file actions
185 lines (173 loc) Β· 5.39 KB
/
previewOptions.js
File metadata and controls
185 lines (173 loc) Β· 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from '@tiptap/pm/view'
import extractLinkParagraphs from './extractLinkParagraphs.js'
import Vue from 'vue'
import PreviewOptions from '../components/Editor/PreviewOptions.vue'
export const previewOptionsPluginKey = new PluginKey('linkParagraphMenu')
/**
* Preview option decorations ProseMirror plugin
* Add preview options to linkParagraphs.
*
* @param {object} options - options for the plugin
* @param {object} options.editor - the tiptap editor
*
* @return {Plugin<DecorationSet>}
*/
export default function previewOptions({ editor }) {
return new Plugin({
key: previewOptionsPluginKey,
state: {
init(_, { doc }) {
if (!editor.options.editable) {
return { linkParagraphs: [], decorations: DecorationSet.empty }
}
const linkParagraphs = extractLinkParagraphs(doc)
return {
linkParagraphs,
decorations: linkParagraphDecorations(doc, linkParagraphs, editor),
}
},
apply(tr, value, _oldState, newState) {
// Always return empty decorations if editor is not editable
if (!editor.options.editable) {
return { linkParagraphs: [], decorations: DecorationSet.empty }
}
if (!tr.docChanged) {
return value
}
const linkParagraphs = extractLinkParagraphs(newState.doc)
const decorations = mapDecorations(value, tr, linkParagraphs) || linkParagraphDecorations(newState.doc, linkParagraphs, editor)
return { linkParagraphs, decorations }
},
},
props: {
decorations(state) {
if (!editor.options.editable) {
return DecorationSet.empty
}
return this.getState(state).decorations
},
},
})
}
/**
* Map the previous deocrations to current document state
*
* Return false if previewParagraphs changes or decorations would get removed. The latter prevents
* lost decorations in case of replacements.
*
* @param {object} value - previous plugin state
* @param {object} tr - current transaction
* @param {Array} linkParagraphs - array of linkParagraphs
*
* @return {false|DecorationSet}
*/
function mapDecorations(value, tr, linkParagraphs) {
if (linkParagraphsChanged(linkParagraphs, value.linkParagraphs)) {
return false
}
let removedDecorations = false
const decorations = value.decorations.map(tr.mapping, tr.doc, { onRemove: () => { removedDecorations = true } })
return removedDecorations
? false
: decorations
}
/**
* Check if the linkParagraphs provided are equivalent.
*
* @param {Array} current - array of linkParagraphs
* @param {Array} prev - linkParagraphs to compare against
*
* @return {boolean}
*/
function linkParagraphsChanged(current, prev) {
return (current.length !== prev.length)
|| current.some(isDifferentFrom(prev))
}
/**
* Checks if linkParagraphs are different
*
* @param {Array} other - linkParagraphs to compare against
*
* Returns a function to be used to call to Array#some.
* The returned function takes a linkParagraph and an index (as provided by iterators)
*/
const isDifferentFrom = (other) => (linkParagraph, i) => {
return linkParagraph.type !== other[i].type
|| linkParagraph.nodeSize !== other[i].nodeSize
}
/**
* Create anchor decorations for the given linkParagraphs
*
* @param {Document} doc - prosemirror doc
* @param {Array} linkParagraphs - linkParagraphs structure in the doc
* @param {object} editor - tiptap editor
*
* @return {DecorationSet}
*/
function linkParagraphDecorations(doc, linkParagraphs, editor) {
const decorations = linkParagraphs.map((linkParagraph) => decorationForLinkParagraph(linkParagraph, editor))
return DecorationSet.create(doc, decorations)
}
/**
* Create a decoration for the given linkParagraph
*
* @param {object} linkParagraph to decorate
* @param {object} editor - tiptap editor
*
* @return {Decoration}
*/
function decorationForLinkParagraph(linkParagraph, editor) {
return Decoration.widget(
linkParagraph.pos + 1,
previewOptionForLinkParagraph(linkParagraph, editor),
{ side: -1 },
)
}
/**
* Create a previewOptions element for the given linkParagraph
*
* @param {object} linkParagraph - linkParagraph to generate anchor for
* @param {number} linkParagraph.pos - Position of the node
* @param {string} linkParagraph.type - selected type
* @param {string} linkParagraph.href - href of the link
* @param {number} linkParagraph.nodeSize - size of the node
* @param {object} editor - tiptap editor
*
* @return {Element}
*/
function previewOptionForLinkParagraph({ type, href, pos, nodeSize }, editor) {
const el = document.createElement('div')
const Component = Vue.extend(PreviewOptions)
const propsData = { type, href }
const previewOption = new Component({ propsData }).$mount(el)
previewOption.$on('open', () => {
editor.commands.hideLinkBubble()
})
previewOption.$on('toggle', (type) => {
setPreview(pos, type, editor)
})
previewOption.$on('delete', () => {
editor.commands.deleteRange({ from: pos, to: pos + nodeSize })
})
return previewOption.$el
}
/**
*
*
* @param {number} pos - Position of the node
* @param {string} type - selected type
* @param {object} editor - tiptap editor
*/
function setPreview(pos, type, editor) {
const chain = editor.chain().focus().setTextSelection(pos + 1)
if (type !== 'text-only') {
chain.setPreview().run()
} else {
chain.unsetPreview().run()
}
}