@mirus/tiptap-editor 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tiptap-editor.js +1 -1
- package/dist/tiptap-editor.umd.cjs +1 -1
- package/package.json +1 -1
- package/src/App.vue +104 -104
- package/src/tiptap-editor.vue +565 -564
package/src/tiptap-editor.vue
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
1
|
<template>
|
|
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
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<div class="tiptap-editor" tabindex="0">
|
|
4
|
+
<div
|
|
5
|
+
v-if="showMenu && editor"
|
|
6
|
+
class="menubar"
|
|
7
|
+
role="toolbar"
|
|
8
|
+
:aria-controls="id || null"
|
|
9
|
+
>
|
|
10
|
+
<button
|
|
11
|
+
:aria-pressed="`${editor.isActive('bold') ? 'true' : 'false'}`"
|
|
12
|
+
:class="{ 'is-active': editor.isActive('bold') }"
|
|
13
|
+
@keyup.left="toolbarGoLeft"
|
|
14
|
+
@keyup.right="toolbarGoRight"
|
|
15
|
+
@click="editor.chain().focus().toggleBold().run()"
|
|
16
|
+
aria-label="bold"
|
|
17
|
+
value="bold"
|
|
18
|
+
type="button"
|
|
19
|
+
>
|
|
20
|
+
<b>B</b>
|
|
21
|
+
</button>
|
|
22
|
+
<button
|
|
23
|
+
:aria-pressed="`${editor.isActive('italic') ? 'true' : 'false'}`"
|
|
24
|
+
:class="{ 'is-active': editor.isActive('italic') }"
|
|
25
|
+
@click="editor.chain().focus().toggleItalic().run()"
|
|
26
|
+
@keyup.left="toolbarGoLeft"
|
|
27
|
+
@keyup.right="toolbarGoRight"
|
|
28
|
+
value="italic"
|
|
29
|
+
type="button"
|
|
30
|
+
>
|
|
31
|
+
<i>I</i>
|
|
32
|
+
</button>
|
|
33
|
+
<button
|
|
34
|
+
@click="editor.chain().focus().toggleBulletList().run()"
|
|
35
|
+
:class="{ 'is-active': editor.isActive('bulletList') }"
|
|
36
|
+
@keyup.left="toolbarGoLeft"
|
|
37
|
+
@keyup.right="toolbarGoRight"
|
|
38
|
+
aria-label="bullet list"
|
|
39
|
+
value="bulletlist"
|
|
40
|
+
type="button"
|
|
41
|
+
>
|
|
42
|
+
<svg
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
focusable="false"
|
|
45
|
+
data-prefix="fas"
|
|
46
|
+
data-icon="list-ul"
|
|
47
|
+
class="svg-inline--fa fa-list-ul fa-w-16"
|
|
48
|
+
role="img"
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
viewBox="0 0 512 512"
|
|
51
|
+
>
|
|
52
|
+
<path
|
|
53
|
+
fill="currentColor"
|
|
54
|
+
d="M96 96c0 26.51-21.49 48-48 48S0 122.51 0 96s21.49-48 48-48 48 21.49 48 48zM48 208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm0 160c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm96-236h352c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
|
|
55
|
+
></path>
|
|
56
|
+
</svg>
|
|
57
|
+
</button>
|
|
58
|
+
<div class="character-count" v-if="maxCharacterCount && editor">
|
|
59
|
+
<svg
|
|
60
|
+
height="20"
|
|
61
|
+
width="20"
|
|
62
|
+
viewBox="0 0 20 20"
|
|
63
|
+
:class="
|
|
64
|
+
maxCharacterCountExceeded
|
|
65
|
+
? 'character-count__graph--warning'
|
|
66
|
+
: 'character-count__graph'
|
|
67
|
+
"
|
|
68
|
+
>
|
|
69
|
+
<circle r="10" cx="10" cy="10" class="character-count__outer-circle" />
|
|
70
|
+
<circle
|
|
71
|
+
r="5"
|
|
72
|
+
cx="10"
|
|
73
|
+
cy="10"
|
|
74
|
+
fill="transparent"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
stroke-width="10"
|
|
77
|
+
:stroke-dasharray="`calc(${characterCountPercentage}px * 31.4 / 100) 31.4`"
|
|
78
|
+
transform="rotate(-90) translate(-20)"
|
|
79
|
+
/>
|
|
80
|
+
<circle r="6" cx="10" cy="10" class="character-count__inner-circle" />
|
|
81
|
+
</svg>
|
|
82
|
+
<div class="character-count__text" aria-live="polite">
|
|
83
|
+
{{ currentCharacterCount }} / {{ maxCharacterCount }} characters
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<editor-content
|
|
88
|
+
:editor="editor"
|
|
89
|
+
:style="{ height: height }"
|
|
90
|
+
:id="id || null"
|
|
91
|
+
role="textbox"
|
|
92
|
+
class="editor__content"
|
|
93
|
+
aria-label="text area"
|
|
94
|
+
tabindex="-1"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="error-list" :v-show="false" ref="renderedErrors">
|
|
98
|
+
<template v-if="currentWarning">
|
|
99
|
+
<b>{{ currentWarning.message }}</b>
|
|
100
|
+
<div
|
|
101
|
+
v-for="(option, index) in currentOptions"
|
|
102
|
+
:key="option.id"
|
|
103
|
+
class="error-list__item"
|
|
104
|
+
:class="{ selected: navigatedOptionIndex === index }"
|
|
105
|
+
@click="selectOption(option)"
|
|
106
|
+
>
|
|
107
|
+
{{ option.value }}
|
|
108
|
+
</div>
|
|
109
|
+
</template>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
112
|
</template>
|
|
113
113
|
|
|
114
114
|
<script setup>
|
|
@@ -131,102 +131,102 @@ import unescape from 'lodash.unescape';
|
|
|
131
131
|
|
|
132
132
|
const emit = defineEmits(['update:value', 'new-character-count']);
|
|
133
133
|
const props = defineProps({
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
height: { type: String, default: '300px' },
|
|
135
|
+
id: { type: String, default: null },
|
|
136
|
+
value: { type: String, default: '' },
|
|
137
|
+
warnings: { type: Array, default: () => [] },
|
|
138
|
+
maxCharacterCount: { type: Number, default: null },
|
|
139
|
+
placeholder: { type: String, default: 'write your content here...' },
|
|
140
|
+
showMenu: { type: Boolean, default: true },
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
const currentValue = ref(props.value);
|
|
144
144
|
const editor = useEditor({
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
145
|
+
content: props.value,
|
|
146
|
+
parseOptions: { preserveWhitespace: 'full' },
|
|
147
|
+
onCreate: ({ editor }) => {
|
|
148
|
+
currentCharacterCount.value = editor.storage.characterCount.characters();
|
|
149
|
+
},
|
|
150
|
+
onUpdate: ({ getJSON, getHTML, editor }) => {
|
|
151
|
+
currentCharacterCount.value = editor.storage.characterCount.characters();
|
|
152
|
+
currentValue.value = editor.getHTML();
|
|
153
|
+
|
|
154
|
+
emit('update:value', currentValue.value);
|
|
155
|
+
|
|
156
|
+
props.warnings.forEach((warning) => {
|
|
157
|
+
if (warning.length && warning.offset) {
|
|
158
|
+
if (editor.state.selection.head - 1 <= warning.offset) {
|
|
159
|
+
let charCountDiff = currentCharacterCount.value - previousCharacterCount.value;
|
|
160
|
+
charCountDiff += adjustForNewlines(editor);
|
|
161
|
+
warning.offset += charCountDiff;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
previousCharacterCount.value = currentCharacterCount.value;
|
|
166
|
+
previousHTML.value = editor.getHTML();
|
|
167
|
+
editor.commands.focus();
|
|
168
|
+
},
|
|
169
|
+
extensions: [
|
|
170
|
+
Bold,
|
|
171
|
+
BulletList,
|
|
172
|
+
CharacterCount.configure({ limit: props.maxCharacterCount }),
|
|
173
|
+
Document,
|
|
174
|
+
History,
|
|
175
|
+
Italic,
|
|
176
|
+
ListItem,
|
|
177
|
+
Paragraph,
|
|
178
|
+
Placeholder.configure({ placeholder: props.placeholder }),
|
|
179
|
+
Text,
|
|
180
|
+
Warning.configure({
|
|
181
|
+
getErrorWords: getErrorWords,
|
|
182
|
+
onEnter: ({ range, command, virtualNode, text }) => {
|
|
183
|
+
currentWarning.value = errors.value.find((err) => err.value === text);
|
|
184
|
+
currentOptions.value = currentWarning.value.options || [];
|
|
185
|
+
navigatedOptionIndex.value = 0;
|
|
186
|
+
optionRange.value = range;
|
|
187
|
+
renderPopup(virtualNode);
|
|
188
|
+
insertOption.value = command;
|
|
189
|
+
},
|
|
190
|
+
onChange: ({ range, virtualNode, text }) => {
|
|
191
|
+
currentWarning.value = errors.value.find((err) => err.value === text);
|
|
192
|
+
currentOptions.value = currentWarning.value.options || [];
|
|
193
|
+
navigatedOptionIndex.value = 0;
|
|
194
|
+
optionRange.value = range;
|
|
195
|
+
renderPopup(virtualNode);
|
|
196
|
+
},
|
|
197
|
+
onExit: () => {
|
|
198
|
+
navigatedOptionIndex.value = 0;
|
|
199
|
+
currentOptions.value = null;
|
|
200
|
+
optionRange.value = null;
|
|
201
|
+
destroyPopup();
|
|
202
|
+
},
|
|
203
|
+
onKeyDown: ({ event }) => {
|
|
204
|
+
// pressing up arrow
|
|
205
|
+
if (event.keyCode === 38 && currentOptions.value !== null) {
|
|
206
|
+
upHandler();
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
// pressing down arrow
|
|
210
|
+
if (event.keyCode === 40 && currentOptions !== null) {
|
|
211
|
+
downHandler();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
// pressing enter
|
|
215
|
+
if (event.keyCode === 13) {
|
|
216
|
+
return enterHandler();
|
|
217
|
+
}
|
|
218
|
+
// pressing escape
|
|
219
|
+
if (event.keyCode === 27) {
|
|
220
|
+
navigatedOptionIndex.value = 0;
|
|
221
|
+
optionRange.value = null;
|
|
222
|
+
currentOptions.value = null;
|
|
223
|
+
destroyPopup();
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
],
|
|
230
230
|
});
|
|
231
231
|
|
|
232
232
|
// Warnings & errors popup
|
|
@@ -239,64 +239,64 @@ const popup = ref(null);
|
|
|
239
239
|
const renderedErrors = ref(null);
|
|
240
240
|
|
|
241
241
|
watch(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
242
|
+
() => props.warnings,
|
|
243
|
+
(n, o) => {
|
|
244
|
+
if (editor) {
|
|
245
|
+
props.warnings.forEach((warning) => {
|
|
246
|
+
if (warning.length && warning.offset >= 0) {
|
|
247
|
+
warning.offset += getOffsetAdjustment(warning);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// preserve selection after updating warnings
|
|
252
|
+
/* const oldSelection = editor.view.state.selection; */
|
|
253
|
+
|
|
254
|
+
/* editor.commands.setTextSelection({
|
|
255
255
|
from: oldSelection.from,
|
|
256
256
|
to: oldSelection.to,
|
|
257
257
|
}); */
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
// record length of text that was used to generate the list of warnings
|
|
260
|
+
initialCharacterCount.value = currentCharacterCount.value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
263
|
);
|
|
264
264
|
|
|
265
265
|
tippy.setDefaultProps({
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
266
|
+
content: renderedErrors.value,
|
|
267
|
+
trigger: 'mouseenter',
|
|
268
|
+
interactive: true,
|
|
269
|
+
theme: 'tiptap',
|
|
270
|
+
placement: 'top-start',
|
|
271
|
+
inertia: true,
|
|
272
|
+
duration: [400, 200],
|
|
273
|
+
showOnCreate: true,
|
|
274
|
+
arrow: true,
|
|
275
|
+
hideOnClick: false,
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
props.warnings.forEach((warning) => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
if (warning.length && warning.offset >= 0) {
|
|
280
|
+
warning.offset += getOffsetAdjustment(warning);
|
|
281
|
+
}
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
const errors = computed(() => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
285
|
+
if (props.warnings.length < 1) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
return props.warnings.map((mistake) => {
|
|
289
|
+
const isWord = mistake.isWord === undefined ? true : mistake.isWord;
|
|
290
|
+
return {
|
|
291
|
+
overrideClass: mistake.overrideClass,
|
|
292
|
+
isWord: isWord,
|
|
293
|
+
value: isWord ? mistake.value : unescape(mistake.value),
|
|
294
|
+
message: mistake.message,
|
|
295
|
+
offset: mistake.offset,
|
|
296
|
+
length: mistake.length,
|
|
297
|
+
options: (mistake.options || []).map((value, id) => ({ value, id })),
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
// character counting
|
|
@@ -306,399 +306,400 @@ const previousCharacterCount = ref(currentCharacterCount.value);
|
|
|
306
306
|
const previousHTML = ref('');
|
|
307
307
|
|
|
308
308
|
const maxCharacterCountExceeded = computed(
|
|
309
|
-
|
|
309
|
+
() => currentCharacterCount.value >= props.maxCharacterCount
|
|
310
310
|
);
|
|
311
311
|
const characterCountPercentage = computed(() =>
|
|
312
|
-
|
|
312
|
+
Math.round((100 / props.maxCharacterCount) * currentCharacterCount.value)
|
|
313
313
|
);
|
|
314
314
|
|
|
315
315
|
// compliance checks base offset data off the HTML value of the text
|
|
316
316
|
// ProseMirror uses a unique token sequence indexing system - see https://prosemirror.net/docs/guide/#doc.indexing
|
|
317
317
|
// we need to convert warning offsets to the index values ProseMirror expects
|
|
318
318
|
function getOffsetAdjustment(warning) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
319
|
+
// compliance check offsets count all HTML entities as one character, whereas the editor value does not. compensate for
|
|
320
|
+
// this by adding to the offset until it matches (or equals the length of the editor text as a failsafe break from the loop)
|
|
321
|
+
const normalizedString = props.value.normalize('NFKC').replace(/ /g, ' ');
|
|
322
|
+
while (
|
|
323
|
+
normalizedString.substr(warning.offset, warning.value.length) !== warning.value &&
|
|
324
|
+
warning.offset <= normalizedString.length
|
|
325
|
+
) {
|
|
326
|
+
warning.offset++;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// obtain the substring up until the beginning of the warning
|
|
330
|
+
const originalLength = warning.offset;
|
|
331
|
+
let substr = props.value.substr(0, warning.offset);
|
|
332
|
+
|
|
333
|
+
// these tags count as one token, so we replace them with a single space
|
|
334
|
+
substr = substr.replace(/<p>|<\/p>|<li>|<ul>|<\/ul>|<\/li>|<div>|<\/div>/g, ' ');
|
|
335
|
+
|
|
336
|
+
// these tags don't count as a token, so we remove them
|
|
337
|
+
const knownTagRegex = /<strong>|<\/strong>|<em>|<\/em>/g;
|
|
338
|
+
substr = substr.replace(knownTagRegex, '');
|
|
339
|
+
|
|
340
|
+
// return the number of chars the offset should be adjusted by
|
|
341
|
+
return substr.length - originalLength;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
// when the user adds a newline to the text of the editor, the character count stays the same, but ProseMirror's token
|
|
345
345
|
// sequence indexing system adds 2 tokens to the content. We need to adjust warning offsets to account for that
|
|
346
346
|
function adjustForNewlines(editor) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
const regex = /<\/p><p>/g;
|
|
348
|
+
const previousNewlines = (previousHTML.value.match(regex) || []).length;
|
|
349
|
+
const newlines = (editor.getHTML().match(regex) || []).length;
|
|
350
350
|
|
|
351
|
-
|
|
352
|
-
|
|
351
|
+
// multiply the difference in newlines by 2 since each instance counts as 2 tokens
|
|
352
|
+
return (newlines - previousNewlines) * 2;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
function getErrorWords() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
356
|
+
if (errors.value.length < 1) {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
return errors.value.map((err) => ({
|
|
360
|
+
value: err.value,
|
|
361
|
+
overrideClass: err.overrideClass,
|
|
362
|
+
isWord: err.isWord,
|
|
363
|
+
offset: err.offset,
|
|
364
|
+
length: err.length,
|
|
365
|
+
}));
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
function upHandler() {
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
optionsLength = currentOptions.value.length;
|
|
370
|
+
navigatedOptionIndex.value = (navigatedOptionIndex.value + optionsLength - 1) % optionsLength;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
function downHandler() {
|
|
374
|
-
|
|
374
|
+
navigatedOptionIndex.value = (navigatedOptionIndex.value + 1) % currentOptions.value.length;
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
function enterHandler() {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
378
|
+
if (currentOptions.value.length === 0) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const option = currentOptions.value[navigatedOptionIndex.value];
|
|
383
|
+
if (option) {
|
|
384
|
+
selectOption(option);
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
function selectOption(option) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
insertOption.value({
|
|
391
|
+
range: optionRange.value,
|
|
392
|
+
attrs: { id: option.id, label: option.value },
|
|
393
|
+
});
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
function renderPopup(virtualNode) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
397
|
+
if (!popup.value && virtualNode) {
|
|
398
|
+
const rect = virtualNode.getBoundingClientRect();
|
|
399
|
+
popup.value = tippy(document.body, {
|
|
400
|
+
content: renderedErrors.value,
|
|
401
|
+
getReferenceClientRect: () => ({
|
|
402
|
+
width: rect.width,
|
|
403
|
+
height: rect.height,
|
|
404
|
+
left: rect.left,
|
|
405
|
+
right: rect.right,
|
|
406
|
+
top: rect.top,
|
|
407
|
+
bottom: rect.bottom,
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
function destroyPopup() {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
414
|
+
if (popup.value) {
|
|
415
|
+
popup.value.destroy();
|
|
416
|
+
popup.value = null;
|
|
417
|
+
}
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
function toolbarGoLeft(evt) {
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
evt.preventDefault();
|
|
422
|
+
const prevSibling = evt.target.previousSibling;
|
|
423
423
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
if (prevSibling && prevSibling.focus !== undefined) {
|
|
425
|
+
prevSibling.focus();
|
|
426
|
+
}
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
function toolbarGoRight(evt) {
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
evt.preventDefault();
|
|
431
|
+
const nextSibling = evt.target.nextSibling;
|
|
432
432
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
433
|
+
if (nextSibling && nextSibling.focus !== undefined) {
|
|
434
|
+
nextSibling.focus();
|
|
435
|
+
}
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
onBeforeUnmount(() => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
439
|
+
if (popup.value) {
|
|
440
|
+
popup.value.destroy();
|
|
441
|
+
}
|
|
442
442
|
});
|
|
443
443
|
</script>
|
|
444
444
|
|
|
445
445
|
<style lang="scss">
|
|
446
446
|
.error-list {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
447
|
+
.error-list__item {
|
|
448
|
+
padding: 3px;
|
|
449
|
+
border-radius: 2px;
|
|
450
|
+
|
|
451
|
+
&.selected,
|
|
452
|
+
&:hover {
|
|
453
|
+
background-color: rgba(white, 0.2);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
.tiptap-editor {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
459
|
+
border: 1px solid #e5e7eb;
|
|
460
|
+
background: white;
|
|
461
|
+
border-radius: 8px;
|
|
462
|
+
padding: 4px;
|
|
463
|
+
|
|
464
|
+
p.is-empty:first-child::before {
|
|
465
|
+
content: attr(data-placeholder);
|
|
466
|
+
float: left;
|
|
467
|
+
color: #aaa;
|
|
468
|
+
pointer-events: none;
|
|
469
|
+
height: 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.menubar {
|
|
473
|
+
// border-bottom: 1px solid #e5e7eb;
|
|
474
|
+
padding: 4px;
|
|
475
|
+
border-radius: 4px;
|
|
476
|
+
background: #f4f4f5;
|
|
477
|
+
display: flex;
|
|
478
|
+
|
|
479
|
+
button {
|
|
480
|
+
font-size: 14px;
|
|
481
|
+
background-color: transparent;
|
|
482
|
+
border: none;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
height: 30px;
|
|
485
|
+
outline: 50;
|
|
486
|
+
width: 35px;
|
|
487
|
+
vertical-align: bottom;
|
|
488
|
+
border-radius: 4px;
|
|
489
|
+
margin-right: 3px;
|
|
490
|
+
|
|
491
|
+
&:focus {
|
|
492
|
+
outline: 2px solid #3b82f6;
|
|
493
|
+
transition: all 0.08s ease-in-out;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
&.is-active {
|
|
497
|
+
background-color: #d3e3fd;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
&.is-active:focus {
|
|
501
|
+
background-color: #bfd2f9;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
&:not(.is-active):hover {
|
|
505
|
+
background-color: #e5e7eb;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
&:not(.is-active):focus {
|
|
509
|
+
background-color: #e5e7eb;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
svg {
|
|
513
|
+
width: 12px;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.editor__content {
|
|
519
|
+
font-size: 16px;
|
|
520
|
+
outline: 0;
|
|
521
|
+
overflow-y: auto;
|
|
522
|
+
padding: 6px 2px 4px 2px;
|
|
523
|
+
|
|
524
|
+
.underline-red {
|
|
525
|
+
border-bottom: 3px red solid;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.underline-orange {
|
|
529
|
+
border-bottom: 3px orange solid;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.underline-green {
|
|
533
|
+
border-bottom: 3px lightgreen solid;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.underline-blue {
|
|
537
|
+
border-bottom: 3px #3b82f6 solid;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
ul {
|
|
541
|
+
padding: 0px 40px;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.ProseMirror {
|
|
545
|
+
height: 100%;
|
|
546
|
+
padding: 2px 8px 2px 8px;
|
|
547
|
+
border-radius: 4px;
|
|
548
|
+
|
|
549
|
+
&:focus {
|
|
550
|
+
outline: 2px solid #3b82f6;
|
|
551
|
+
transition: all 0.08s ease-in-out;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
.character-count {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
558
|
+
padding: 4px;
|
|
559
|
+
border-radius: 4px;
|
|
560
|
+
text-align: right;
|
|
561
|
+
padding-right: 15px;
|
|
562
|
+
display: flex;
|
|
563
|
+
gap: 8px;
|
|
564
|
+
justify-content: flex-end;
|
|
565
|
+
align-items: center;
|
|
566
|
+
flex-grow: 2;
|
|
567
|
+
|
|
568
|
+
&__graph {
|
|
569
|
+
color: #a8c2f7;
|
|
570
|
+
|
|
571
|
+
&--warning {
|
|
572
|
+
color: #fb7373;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
&__inner-circle {
|
|
577
|
+
fill: #f4f4f5;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
&__outer-circle {
|
|
581
|
+
fill: #ffffff;
|
|
582
|
+
}
|
|
582
583
|
}
|
|
583
584
|
|
|
584
585
|
.tippy-box[data-theme~='tiptap'] {
|
|
585
|
-
|
|
586
|
+
background-color: #0a0a0a;
|
|
586
587
|
}
|
|
587
588
|
.tippy-box[data-theme~='tiptap'] > .tippy-arrow {
|
|
588
|
-
|
|
589
|
-
|
|
589
|
+
width: 14px;
|
|
590
|
+
height: 14px;
|
|
590
591
|
}
|
|
591
592
|
.tippy-box[data-theme~='tiptap'][data-placement^='top'] > .tippy-arrow:before {
|
|
592
|
-
|
|
593
|
-
|
|
593
|
+
border-width: 7px 7px 0;
|
|
594
|
+
border-top-color: #0a0a0a;
|
|
594
595
|
}
|
|
595
596
|
.tippy-box[data-theme~='tiptap'][data-placement^='bottom'] > .tippy-arrow:before {
|
|
596
|
-
|
|
597
|
-
|
|
597
|
+
border-width: 0 7px 7px;
|
|
598
|
+
border-bottom-color: #0a0a0a;
|
|
598
599
|
}
|
|
599
600
|
.tippy-box[data-theme~='tiptap'][data-placement^='left'] > .tippy-arrow:before {
|
|
600
|
-
|
|
601
|
-
|
|
601
|
+
border-width: 7px 0 7px 7px;
|
|
602
|
+
border-left-color: #0a0a0a;
|
|
602
603
|
}
|
|
603
604
|
.tippy-box[data-theme~='tiptap'][data-placement^='right'] > .tippy-arrow:before {
|
|
604
|
-
|
|
605
|
-
|
|
605
|
+
border-width: 7px 7px 7px 0;
|
|
606
|
+
border-right-color: #0a0a0a;
|
|
606
607
|
}
|
|
607
608
|
.tippy-box[data-theme~='tiptap'] > .tippy-backdrop {
|
|
608
|
-
|
|
609
|
+
background-color: #0a0a0a;
|
|
609
610
|
}
|
|
610
611
|
.tippy-box[data-theme~='tiptap'] > .tippy-svg-arrow {
|
|
611
|
-
|
|
612
|
+
fill: #0a0a0a;
|
|
612
613
|
}
|
|
613
614
|
|
|
614
615
|
html[color-scheme='dark'] {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
616
|
+
.tiptap-editor {
|
|
617
|
+
border: 2px solid #27272a;
|
|
618
|
+
background: #18181b;
|
|
619
|
+
color: #e4e4e7;
|
|
620
|
+
|
|
621
|
+
.menubar {
|
|
622
|
+
background: #27272a;
|
|
623
|
+
color: #d1d5db;
|
|
624
|
+
|
|
625
|
+
button {
|
|
626
|
+
color: #e4e4e7;
|
|
627
|
+
|
|
628
|
+
&:focus {
|
|
629
|
+
outline: 2px solid #3b82f6;
|
|
630
|
+
transition: all 0.08s ease-in-out;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
&.is-active {
|
|
634
|
+
background-color: #515e75;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
&.is-active:focus {
|
|
638
|
+
background-color: #515e75;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
&:not(.is-active):hover {
|
|
642
|
+
background-color: #3f3f46;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
&:not(.is-active):focus {
|
|
646
|
+
background-color: #3f3f46;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
svg {
|
|
650
|
+
width: 12px;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.character-count {
|
|
657
|
+
&__graph {
|
|
658
|
+
color: #a8c2f7;
|
|
659
|
+
|
|
660
|
+
&--warning {
|
|
661
|
+
color: #fb7373;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
&__inner-circle {
|
|
666
|
+
fill: #27272a;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
&__outer-circle {
|
|
670
|
+
fill: #27272a;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.tippy-box[data-theme~='tiptap'] {
|
|
675
|
+
background-color: #d1d5db;
|
|
676
|
+
color: #111827;
|
|
677
|
+
}
|
|
678
|
+
.tippy-box[data-theme~='tiptap'][data-placement^='top'] > .tippy-arrow:before {
|
|
679
|
+
border-top-color: #d1d5db;
|
|
680
|
+
}
|
|
681
|
+
.tippy-box[data-theme~='tiptap'][data-placement^='bottom'] > .tippy-arrow:before {
|
|
682
|
+
border-bottom-color: #d1d5db;
|
|
683
|
+
}
|
|
684
|
+
.tippy-box[data-theme~='tiptap'][data-placement^='left'] > .tippy-arrow:before {
|
|
685
|
+
border-left-color: #d1d5db;
|
|
686
|
+
}
|
|
687
|
+
.tippy-box[data-theme~='tiptap'][data-placement^='right'] > .tippy-arrow:before {
|
|
688
|
+
border-right-color: #d1d5db;
|
|
689
|
+
}
|
|
690
|
+
.tippy-box[data-theme~='tiptap'] > .tippy-backdrop {
|
|
691
|
+
background-color: #d1d5db;
|
|
692
|
+
}
|
|
693
|
+
.tippy-box[data-theme~='tiptap'] > .tippy-svg-arrow {
|
|
694
|
+
fill: #d1d5db;
|
|
695
|
+
}
|
|
696
|
+
.error-list {
|
|
697
|
+
.error-list__item {
|
|
698
|
+
&.selected,
|
|
699
|
+
&:hover {
|
|
700
|
+
background-color: rgba(black, 0.15);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
703
704
|
}
|
|
704
705
|
</style>
|