@humandialog/forms.svelte 1.1.7 → 1.2.1
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/components/document/editor.svelte +546 -0
- package/components/document/editor.svelte.d.ts +33 -0
- package/components/document/internal/findSuggestionMatch.d.ts +16 -0
- package/components/document/internal/findSuggestionMatch.js +51 -0
- package/components/document/internal/palette.svelte +12 -4
- package/components/document/internal/palette.svelte.d.ts +12 -2
- package/components/document/internal/suggestion.d.ts +158 -0
- package/components/document/internal/suggestion.js +194 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +8 -1
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
<script>import { onMount, onDestroy, getContext } from "svelte";
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
4
|
+
import Document from "@tiptap/extension-document";
|
|
5
|
+
import Paragraph from "@tiptap/extension-paragraph";
|
|
6
|
+
import Text from "@tiptap/extension-text";
|
|
7
|
+
import Heading from "@tiptap/extension-heading";
|
|
8
|
+
import Blockquote from "@tiptap/extension-blockquote";
|
|
9
|
+
import HardBreak from "@tiptap/extension-hard-break";
|
|
10
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
11
|
+
import Bold from "@tiptap/extension-bold";
|
|
12
|
+
import Code from "@tiptap/extension-code";
|
|
13
|
+
import Italic from "@tiptap/extension-italic";
|
|
14
|
+
import Strike from "@tiptap/extension-strike";
|
|
15
|
+
import Dropcursor from "@tiptap/extension-dropcursor";
|
|
16
|
+
import Gapcursor from "@tiptap/extension-gapcursor";
|
|
17
|
+
import History from "@tiptap/extension-history";
|
|
18
|
+
import { data_tick_store, contextItemsStore, contextTypesStore } from "../../stores.js";
|
|
19
|
+
import { informModification, pushChanges } from "../../updates.js";
|
|
20
|
+
import { isDeviceSmallerThan, parseWidthDirective } from "../../utils.js";
|
|
21
|
+
import Palette from "./internal/palette.svelte";
|
|
22
|
+
import { FaFont, FaRemoveFormat, FaCode, FaComment, FaQuoteRight, FaExclamationTriangle, FaInfo } from "svelte-icons/fa";
|
|
23
|
+
import IcH1 from "./internal/h1.icon.svelte";
|
|
24
|
+
import IcH2 from "./internal/h2.icon.svelte";
|
|
25
|
+
import IcH3 from "./internal/h3.icon.svelte";
|
|
26
|
+
import IcH4 from "./internal/h4.icon.svelte";
|
|
27
|
+
import { Extension } from "@tiptap/core";
|
|
28
|
+
import { Suggestion } from "./internal/suggestion";
|
|
29
|
+
export let value = "";
|
|
30
|
+
export let placeholder = "";
|
|
31
|
+
export let self = null;
|
|
32
|
+
export let a = "";
|
|
33
|
+
export let context = "";
|
|
34
|
+
export let typename = "";
|
|
35
|
+
export let compact = false;
|
|
36
|
+
export let onFocusCb = void 0;
|
|
37
|
+
export let onBlurCb = void 0;
|
|
38
|
+
export let c = "";
|
|
39
|
+
export let pushChangesImmediately = true;
|
|
40
|
+
let onFinishEditing = void 0;
|
|
41
|
+
export function run(onStop = void 0) {
|
|
42
|
+
onFinishEditing = onStop;
|
|
43
|
+
editor.commands.focus();
|
|
44
|
+
}
|
|
45
|
+
export function getFormattingOperations(withCaptions = false) {
|
|
46
|
+
let result = [];
|
|
47
|
+
commands.forEach((c2) => {
|
|
48
|
+
result.push({
|
|
49
|
+
caption: withCaptions ? c2.caption : "",
|
|
50
|
+
icon: c2.icon,
|
|
51
|
+
action: c2.on_choice
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
let item = null;
|
|
57
|
+
let changedValue = "";
|
|
58
|
+
let hasChangedValue = false;
|
|
59
|
+
let additional_class = $$restProps["class"] ?? "";
|
|
60
|
+
let ctx = context ? context : getContext("ctx");
|
|
61
|
+
let cs = parseWidthDirective(c);
|
|
62
|
+
let appearance_class;
|
|
63
|
+
if (compact)
|
|
64
|
+
appearance_class = "";
|
|
65
|
+
else
|
|
66
|
+
appearance_class = `bg-stone-50 border border-stone-300 rounded-md
|
|
67
|
+
dark:bg-stone-700 dark:border-stone-600
|
|
68
|
+
px-2.5`;
|
|
69
|
+
let last_tick = -1;
|
|
70
|
+
$: {
|
|
71
|
+
if (last_tick < $data_tick_store) {
|
|
72
|
+
if (hasChangedValue)
|
|
73
|
+
saveData();
|
|
74
|
+
else
|
|
75
|
+
setupSource();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function setupSource() {
|
|
79
|
+
last_tick = $data_tick_store;
|
|
80
|
+
if (a) {
|
|
81
|
+
if (self)
|
|
82
|
+
item = self;
|
|
83
|
+
else
|
|
84
|
+
item = $contextItemsStore[ctx];
|
|
85
|
+
if (!typename)
|
|
86
|
+
typename = $contextTypesStore[ctx];
|
|
87
|
+
if (item != null)
|
|
88
|
+
value = item[a];
|
|
89
|
+
}
|
|
90
|
+
if (!value)
|
|
91
|
+
value = "<p></p>";
|
|
92
|
+
hasChangedValue = false;
|
|
93
|
+
}
|
|
94
|
+
const PalletePlugin = Extension.create({
|
|
95
|
+
name: "palette",
|
|
96
|
+
addOptions() {
|
|
97
|
+
return {
|
|
98
|
+
suggestion: {
|
|
99
|
+
char: "/",
|
|
100
|
+
decorationTag: "span",
|
|
101
|
+
decorationClass: "suggestion",
|
|
102
|
+
startOfLine: false,
|
|
103
|
+
allowSpaces: false,
|
|
104
|
+
//prefixSpace: false,
|
|
105
|
+
allowedPrefixes: null,
|
|
106
|
+
command: ({ editor: editor2, range, props }) => {
|
|
107
|
+
props.command({ editor: editor2, range });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
addProseMirrorPlugins() {
|
|
113
|
+
return [
|
|
114
|
+
Suggestion({
|
|
115
|
+
editor: this.editor,
|
|
116
|
+
...this.options.suggestion
|
|
117
|
+
})
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const suggestion = {
|
|
122
|
+
items: ({ query }) => {
|
|
123
|
+
},
|
|
124
|
+
render: () => {
|
|
125
|
+
return {
|
|
126
|
+
onStart: (props) => {
|
|
127
|
+
if (!props.clientRect) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
show_command_palette();
|
|
131
|
+
palette.set_current_editor_range(props.range);
|
|
132
|
+
palette.filter("");
|
|
133
|
+
},
|
|
134
|
+
onUpdate(props) {
|
|
135
|
+
if (is_command_palette_visible) {
|
|
136
|
+
palette.set_current_editor_range(props.range);
|
|
137
|
+
palette.filter(props.query);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
onKeyDown(props) {
|
|
141
|
+
if (is_command_palette_visible) {
|
|
142
|
+
palette.set_current_editor_range(props.range);
|
|
143
|
+
switch (props.event.key) {
|
|
144
|
+
case "Escape":
|
|
145
|
+
palette.hide();
|
|
146
|
+
return true;
|
|
147
|
+
case "ArrowUp":
|
|
148
|
+
palette.navigate_up();
|
|
149
|
+
return true;
|
|
150
|
+
case "ArrowDown":
|
|
151
|
+
palette.navigate_down();
|
|
152
|
+
return true;
|
|
153
|
+
case "Enter":
|
|
154
|
+
palette.execute_selected_command();
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
},
|
|
160
|
+
onExit() {
|
|
161
|
+
if (is_command_palette_visible)
|
|
162
|
+
palette.hide();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
let editorElement;
|
|
168
|
+
let editor;
|
|
169
|
+
const codeBlockClass = "ml-6 text-sm font-mono break-normal text-pink-700 dark:text-pink-600";
|
|
170
|
+
const CodeBlock = Paragraph.extend({
|
|
171
|
+
name: "CodeBlock",
|
|
172
|
+
priority: 999,
|
|
173
|
+
addAttributes() {
|
|
174
|
+
return {
|
|
175
|
+
class: {
|
|
176
|
+
default: codeBlockClass
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
renderHTML({ HTMLAttributes }) {
|
|
181
|
+
return ["p", { class: codeBlockClass }, 0];
|
|
182
|
+
},
|
|
183
|
+
parseHTML() {
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
tag: "p",
|
|
187
|
+
getAttrs: (node) => node.className === codeBlockClass && null,
|
|
188
|
+
priority: 51
|
|
189
|
+
}
|
|
190
|
+
];
|
|
191
|
+
},
|
|
192
|
+
addCommands() {
|
|
193
|
+
return {
|
|
194
|
+
setAsCode: () => ({ commands: commands2 }) => {
|
|
195
|
+
return commands2.setNode(this.name);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
const commentBlockClass = "ml-6 text-sm italic text-lime-700 dark:text-lime-600";
|
|
201
|
+
const CommentBlock = Paragraph.extend({
|
|
202
|
+
name: "CommentBlock",
|
|
203
|
+
priority: 999,
|
|
204
|
+
addAttributes() {
|
|
205
|
+
return {
|
|
206
|
+
class: {
|
|
207
|
+
default: commentBlockClass
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
renderHTML({ HTMLAttributes }) {
|
|
212
|
+
return ["p", { class: commentBlockClass }, 0];
|
|
213
|
+
},
|
|
214
|
+
parseHTML() {
|
|
215
|
+
return [
|
|
216
|
+
{
|
|
217
|
+
tag: "p",
|
|
218
|
+
getAttrs: (node) => node.className === commentBlockClass && null,
|
|
219
|
+
priority: 51
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
},
|
|
223
|
+
addCommands() {
|
|
224
|
+
return {
|
|
225
|
+
setAsComment: () => ({ commands: commands2 }) => {
|
|
226
|
+
return commands2.setNode(this.name);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
const warnBlockClass = "ml-6 px-3 py-1 border-l-2 rounded-md border-orange-500 bg-orange-100 dark:bg-orange-900";
|
|
232
|
+
const WarningBlock = Paragraph.extend({
|
|
233
|
+
name: "WarningBlock",
|
|
234
|
+
priority: 999,
|
|
235
|
+
addAttributes() {
|
|
236
|
+
return {
|
|
237
|
+
class: {
|
|
238
|
+
default: warnBlockClass
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
renderHTML({ HTMLAttributes }) {
|
|
243
|
+
return ["p", { class: warnBlockClass }, 0];
|
|
244
|
+
},
|
|
245
|
+
parseHTML() {
|
|
246
|
+
return [
|
|
247
|
+
{
|
|
248
|
+
tag: "p",
|
|
249
|
+
getAttrs: (node) => node.className === warnBlockClass && null,
|
|
250
|
+
priority: 51
|
|
251
|
+
}
|
|
252
|
+
];
|
|
253
|
+
},
|
|
254
|
+
addCommands() {
|
|
255
|
+
return {
|
|
256
|
+
setAsWarning: () => ({ commands: commands2 }) => {
|
|
257
|
+
return commands2.setNode(this.name);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const infoBlockClass = "ml-6 px-3 py-1 border-l-2 rounded-md border-blue-500 bg-blue-100 dark:bg-blue-900";
|
|
263
|
+
const InfoBlock = Paragraph.extend({
|
|
264
|
+
name: "InfoBlock",
|
|
265
|
+
priority: 999,
|
|
266
|
+
addAttributes() {
|
|
267
|
+
return {
|
|
268
|
+
class: {
|
|
269
|
+
default: infoBlockClass
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
renderHTML({ HTMLAttributes }) {
|
|
274
|
+
return ["p", { class: infoBlockClass }, 0];
|
|
275
|
+
},
|
|
276
|
+
parseHTML() {
|
|
277
|
+
return [
|
|
278
|
+
{
|
|
279
|
+
tag: "p",
|
|
280
|
+
getAttrs: (node) => node.className === infoBlockClass && null,
|
|
281
|
+
priority: 51
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
},
|
|
285
|
+
addCommands() {
|
|
286
|
+
return {
|
|
287
|
+
setAsInfo: () => ({ commands: commands2 }) => {
|
|
288
|
+
return commands2.setNode(this.name);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
const QuoteBlock = Paragraph.extend({
|
|
294
|
+
name: "QuoteBlock",
|
|
295
|
+
priority: 998,
|
|
296
|
+
renderHTML({ HTMLAttributes }) {
|
|
297
|
+
return ["blockquote", HTMLAttributes, 0];
|
|
298
|
+
},
|
|
299
|
+
parseHTML() {
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
tag: "blockquote",
|
|
303
|
+
priority: 51
|
|
304
|
+
}
|
|
305
|
+
];
|
|
306
|
+
},
|
|
307
|
+
addCommands() {
|
|
308
|
+
return {
|
|
309
|
+
setAsQuote: () => ({ commands: commands2 }) => {
|
|
310
|
+
return commands2.setNode(this.name);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
onMount(() => {
|
|
316
|
+
editor = new Editor({
|
|
317
|
+
editorProps: {
|
|
318
|
+
attributes: {
|
|
319
|
+
class: `${cs} ${appearance_class} prose prose-base sm:prose-base dark:prose-invert ${additional_class} overflow-y-auto whitespace-pre-wrap focus:outline-none`
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
element: editorElement,
|
|
323
|
+
extensions: [
|
|
324
|
+
Document,
|
|
325
|
+
Paragraph,
|
|
326
|
+
Text,
|
|
327
|
+
Heading.configure({
|
|
328
|
+
levels: [1, 2]
|
|
329
|
+
}),
|
|
330
|
+
HardBreak,
|
|
331
|
+
HorizontalRule,
|
|
332
|
+
// custom
|
|
333
|
+
CodeBlock,
|
|
334
|
+
CommentBlock,
|
|
335
|
+
WarningBlock,
|
|
336
|
+
InfoBlock,
|
|
337
|
+
QuoteBlock,
|
|
338
|
+
Bold,
|
|
339
|
+
Code,
|
|
340
|
+
Italic,
|
|
341
|
+
Strike,
|
|
342
|
+
Dropcursor,
|
|
343
|
+
Gapcursor,
|
|
344
|
+
History,
|
|
345
|
+
PalletePlugin.configure({
|
|
346
|
+
suggestion
|
|
347
|
+
})
|
|
348
|
+
],
|
|
349
|
+
content: value,
|
|
350
|
+
injectCSS: false,
|
|
351
|
+
onTransaction({ editor: editor2, transaction }) {
|
|
352
|
+
hasChangedValue = true;
|
|
353
|
+
changedValue = editor2.getHTML();
|
|
354
|
+
},
|
|
355
|
+
onFocus({ editor: editor2, event }) {
|
|
356
|
+
if (onFocusCb)
|
|
357
|
+
onFocusCb();
|
|
358
|
+
},
|
|
359
|
+
onBlur({ editor: editor2, event }) {
|
|
360
|
+
on_blur();
|
|
361
|
+
if (onBlurCb)
|
|
362
|
+
onBlurCb();
|
|
363
|
+
},
|
|
364
|
+
onContentError({ editor: editor2, error, disableCollaboration }) {
|
|
365
|
+
console.log("editor content error:", error);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
onDestroy(() => {
|
|
370
|
+
if (editor) {
|
|
371
|
+
editor.destroy();
|
|
372
|
+
}
|
|
373
|
+
if (is_command_palette_visible)
|
|
374
|
+
palette.hide();
|
|
375
|
+
});
|
|
376
|
+
function on_blur() {
|
|
377
|
+
if (onFinishEditing) {
|
|
378
|
+
onFinishEditing();
|
|
379
|
+
onFinishEditing = void 0;
|
|
380
|
+
}
|
|
381
|
+
if (saveData()) {
|
|
382
|
+
last_tick = $data_tick_store + 1;
|
|
383
|
+
$data_tick_store = last_tick;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
export function save() {
|
|
387
|
+
if (saveData()) {
|
|
388
|
+
last_tick = $data_tick_store + 1;
|
|
389
|
+
$data_tick_store = last_tick;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function saveData() {
|
|
393
|
+
if (item && a && hasChangedValue) {
|
|
394
|
+
item[a] = changedValue;
|
|
395
|
+
hasChangedValue = false;
|
|
396
|
+
if (typename)
|
|
397
|
+
informModification(item, a, typename);
|
|
398
|
+
else
|
|
399
|
+
informModification(item, a);
|
|
400
|
+
if (pushChangesImmediately)
|
|
401
|
+
pushChanges();
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
let palette = null;
|
|
407
|
+
let is_command_palette_visible = false;
|
|
408
|
+
function get_selection_bbox() {
|
|
409
|
+
const selection = window.getSelection();
|
|
410
|
+
const range = selection.getRangeAt(0);
|
|
411
|
+
const rect = range.getBoundingClientRect();
|
|
412
|
+
return rect;
|
|
413
|
+
}
|
|
414
|
+
function get_window_box() {
|
|
415
|
+
let client_rect = new DOMRect();
|
|
416
|
+
client_rect.x = 0;
|
|
417
|
+
client_rect.y = 0;
|
|
418
|
+
client_rect.width = window.innerWidth;
|
|
419
|
+
client_rect.height = window.innerHeight;
|
|
420
|
+
return client_rect;
|
|
421
|
+
}
|
|
422
|
+
function on_palette_mouse_click() {
|
|
423
|
+
}
|
|
424
|
+
function on_palette_shown() {
|
|
425
|
+
is_command_palette_visible = true;
|
|
426
|
+
}
|
|
427
|
+
function on_palette_hidden() {
|
|
428
|
+
is_command_palette_visible = false;
|
|
429
|
+
}
|
|
430
|
+
function show_command_palette() {
|
|
431
|
+
let rect = get_selection_bbox();
|
|
432
|
+
let client_rect = get_window_box();
|
|
433
|
+
let top_space = rect.y;
|
|
434
|
+
let bottom_space = client_rect.height - (rect.y + rect.height);
|
|
435
|
+
palette.max_height_px = 500;
|
|
436
|
+
palette.width_px = 400;
|
|
437
|
+
let preferred_palette_height = palette.max_height_px;
|
|
438
|
+
let preferred_palette_width = palette.width_px;
|
|
439
|
+
let x = 0, y = 0;
|
|
440
|
+
let show_above = false;
|
|
441
|
+
let show_fullscreen = false;
|
|
442
|
+
if (client_rect.width < preferred_palette_width)
|
|
443
|
+
show_fullscreen = true;
|
|
444
|
+
else if (rect.x + preferred_palette_width > client_rect.width)
|
|
445
|
+
x = client_rect.width - preferred_palette_width;
|
|
446
|
+
else
|
|
447
|
+
x = rect.x;
|
|
448
|
+
if (client_rect.height < preferred_palette_height)
|
|
449
|
+
show_fullscreen = true;
|
|
450
|
+
else if (bottom_space >= preferred_palette_height)
|
|
451
|
+
y = rect.y + rect.height;
|
|
452
|
+
else if (top_space >= preferred_palette_height) {
|
|
453
|
+
y = rect.y;
|
|
454
|
+
show_above = true;
|
|
455
|
+
} else if (rect.x > preferred_palette_width) {
|
|
456
|
+
x = rect.x - preferred_palette_width - 5;
|
|
457
|
+
y = rect.y - (preferred_palette_height - bottom_space);
|
|
458
|
+
} else if (rect.x + preferred_palette_width < client_rect.width) {
|
|
459
|
+
x = rect.x + 5;
|
|
460
|
+
y = rect.y - (preferred_palette_height - bottom_space);
|
|
461
|
+
} else
|
|
462
|
+
show_fullscreen = true;
|
|
463
|
+
const isSmallDevice = isDeviceSmallerThan("sm");
|
|
464
|
+
if (isSmallDevice) {
|
|
465
|
+
palette.showAsToolbox(rect);
|
|
466
|
+
} else if (show_fullscreen)
|
|
467
|
+
palette.show_fullscreen(client_rect.width, client_rect.height);
|
|
468
|
+
else
|
|
469
|
+
palette.show(x, y, show_above);
|
|
470
|
+
}
|
|
471
|
+
let commands = [
|
|
472
|
+
{ caption: "Normal", description: "This is normal text style", tags: "text", icon: FaRemoveFormat, on_choice: (range) => {
|
|
473
|
+
if (range)
|
|
474
|
+
editor.chain().focus().deleteRange(range).setParagraph().run();
|
|
475
|
+
else
|
|
476
|
+
editor.commands.setParagraph();
|
|
477
|
+
} },
|
|
478
|
+
{ caption: "Heading 1", description: "Description heading", tags: "h1", icon: IcH1, on_choice: (range) => {
|
|
479
|
+
if (range)
|
|
480
|
+
editor.chain().focus().deleteRange(range).setHeading({ level: 1 }).run();
|
|
481
|
+
else
|
|
482
|
+
editor.commands.setHeading({ level: 1 });
|
|
483
|
+
} },
|
|
484
|
+
{ caption: "Heading 2", description: "Description heading", tags: "h2", icon: IcH2, on_choice: (range) => {
|
|
485
|
+
if (range)
|
|
486
|
+
editor.chain().focus().deleteRange(range).setHeading({ level: 2 }).run();
|
|
487
|
+
else
|
|
488
|
+
editor.commands.setHeading({ level: 2 });
|
|
489
|
+
} },
|
|
490
|
+
{ caption: "Code", description: "Source code monospace text", icon: FaCode, on_choice: (range) => {
|
|
491
|
+
if (range)
|
|
492
|
+
editor.chain().focus().deleteRange(range).setAsCode().run();
|
|
493
|
+
else
|
|
494
|
+
editor.commands.setAsCode();
|
|
495
|
+
} },
|
|
496
|
+
{ caption: "Comment", description: "With this you can comment the above paragraph", icon: FaComment, on_choice: (range) => {
|
|
497
|
+
if (range)
|
|
498
|
+
editor.chain().focus().deleteRange(range).setAsComment().run();
|
|
499
|
+
else
|
|
500
|
+
editor.commands.setAsComment();
|
|
501
|
+
} },
|
|
502
|
+
{ caption: "Quote", description: "To quote someone", icon: FaQuoteRight, on_choice: (range) => {
|
|
503
|
+
if (range)
|
|
504
|
+
editor.chain().focus().deleteRange(range).setAsQuote().run();
|
|
505
|
+
else
|
|
506
|
+
editor.commands.setAsQuote();
|
|
507
|
+
} },
|
|
508
|
+
{ caption: "Warning", description: "An important warning to above paragraph", icon: FaExclamationTriangle, on_choice: (range) => {
|
|
509
|
+
if (range)
|
|
510
|
+
editor.chain().focus().deleteRange(range).setAsWarning().run();
|
|
511
|
+
else
|
|
512
|
+
editor.commands.setAsWarning();
|
|
513
|
+
} },
|
|
514
|
+
{ caption: "Info", description: "An important info about above paragraph", icon: FaInfo, on_choice: (range) => {
|
|
515
|
+
if (range)
|
|
516
|
+
editor.chain().focus().deleteRange(range).setAsInfo().run();
|
|
517
|
+
else
|
|
518
|
+
editor.commands.setAsInfo();
|
|
519
|
+
} }
|
|
520
|
+
];
|
|
521
|
+
</script>
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
<div bind:this={editorElement} />
|
|
526
|
+
|
|
527
|
+
<Palette commands={commands}
|
|
528
|
+
bind:this={palette}
|
|
529
|
+
on:palette_shown={on_palette_shown}
|
|
530
|
+
on:palette_hidden={on_palette_hidden}
|
|
531
|
+
on:palette_mouse_click={on_palette_mouse_click}/>
|
|
532
|
+
|
|
533
|
+
<!--div contenteditable="true"
|
|
534
|
+
bind:this={editorElement}
|
|
535
|
+
on:keyup={on_keyup}
|
|
536
|
+
on:keydown={on_keydown}
|
|
537
|
+
on:mouseup={on_mouseup}
|
|
538
|
+
class="{cs} {appearance_class}
|
|
539
|
+
prose prose-base sm:prose-base dark:prose-invert {additional_class} overflow-y-auto whitespace-pre-wrap"
|
|
540
|
+
on:blur={on_blur}
|
|
541
|
+
on:focus={on_focus}
|
|
542
|
+
on:focus
|
|
543
|
+
on:blur
|
|
544
|
+
>
|
|
545
|
+
{@html value}
|
|
546
|
+
</div-->
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
[x: string]: any;
|
|
5
|
+
value?: string | undefined;
|
|
6
|
+
placeholder?: string | undefined;
|
|
7
|
+
self?: null | undefined;
|
|
8
|
+
a?: string | undefined;
|
|
9
|
+
context?: string | undefined;
|
|
10
|
+
typename?: string | undefined;
|
|
11
|
+
compact?: boolean | undefined;
|
|
12
|
+
onFocusCb?: undefined;
|
|
13
|
+
onBlurCb?: undefined;
|
|
14
|
+
c?: string | undefined;
|
|
15
|
+
pushChangesImmediately?: boolean | undefined;
|
|
16
|
+
run?: ((onStop?: undefined) => void) | undefined;
|
|
17
|
+
getFormattingOperations?: ((withCaptions?: boolean) => any[]) | undefined;
|
|
18
|
+
save?: (() => void) | undefined;
|
|
19
|
+
};
|
|
20
|
+
events: {
|
|
21
|
+
[evt: string]: CustomEvent<any>;
|
|
22
|
+
};
|
|
23
|
+
slots: {};
|
|
24
|
+
};
|
|
25
|
+
export type EditorProps = typeof __propDef.props;
|
|
26
|
+
export type EditorEvents = typeof __propDef.events;
|
|
27
|
+
export type EditorSlots = typeof __propDef.slots;
|
|
28
|
+
export default class Editor extends SvelteComponentTyped<EditorProps, EditorEvents, EditorSlots> {
|
|
29
|
+
get run(): (onStop?: undefined) => void;
|
|
30
|
+
get getFormattingOperations(): (withCaptions?: boolean) => any[];
|
|
31
|
+
get save(): () => void;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Range } from '@tiptap/core';
|
|
2
|
+
import { ResolvedPos } from '@tiptap/pm/model';
|
|
3
|
+
export interface Trigger {
|
|
4
|
+
char: string;
|
|
5
|
+
allowSpaces: boolean;
|
|
6
|
+
allowToIncludeChar: boolean;
|
|
7
|
+
allowedPrefixes: string[] | null;
|
|
8
|
+
startOfLine: boolean;
|
|
9
|
+
$position: ResolvedPos;
|
|
10
|
+
}
|
|
11
|
+
export type SuggestionMatch = {
|
|
12
|
+
range: Range;
|
|
13
|
+
query: string;
|
|
14
|
+
text: string;
|
|
15
|
+
} | null;
|
|
16
|
+
export declare function findSuggestionMatch(config: Trigger): SuggestionMatch;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { escapeForRegEx } from '@tiptap/core';
|
|
2
|
+
import {} from '@tiptap/core';
|
|
3
|
+
import { ResolvedPos } from '@tiptap/pm/model';
|
|
4
|
+
export function findSuggestionMatch(config) {
|
|
5
|
+
const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position, } = config;
|
|
6
|
+
const allowSpaces = allowSpacesOption && !allowToIncludeChar;
|
|
7
|
+
const escapedChar = escapeForRegEx(char);
|
|
8
|
+
const suffix = new RegExp(`\\s${escapedChar}$`);
|
|
9
|
+
const prefix = startOfLine ? '^' : '';
|
|
10
|
+
const finalEscapedChar = allowToIncludeChar ? '' : escapedChar;
|
|
11
|
+
const regexp = allowSpaces
|
|
12
|
+
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, 'gm')
|
|
13
|
+
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, 'gm');
|
|
14
|
+
const text = $position.nodeBefore?.isText && $position.nodeBefore.text;
|
|
15
|
+
if (!text) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const textFrom = $position.pos - text.length;
|
|
19
|
+
const match = Array.from(text.matchAll(regexp)).pop();
|
|
20
|
+
if (!match || match.input === undefined || match.index === undefined) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
// JavaScript doesn't have lookbehinds. This hacks a check that first character
|
|
24
|
+
// is a space or the start of the line
|
|
25
|
+
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
|
|
26
|
+
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes?.join('')}\0]?$`).test(matchPrefix);
|
|
27
|
+
if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
// The absolute position of the match in the document
|
|
31
|
+
const from = textFrom + match.index;
|
|
32
|
+
let to = from + match[0].length;
|
|
33
|
+
// Edge case handling; if spaces are allowed and we're directly in between
|
|
34
|
+
// two triggers
|
|
35
|
+
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
|
36
|
+
match[0] += ' ';
|
|
37
|
+
to += 1;
|
|
38
|
+
}
|
|
39
|
+
// If the $position is located within the matched substring, return that range
|
|
40
|
+
if (from < $position.pos && to >= $position.pos) {
|
|
41
|
+
return {
|
|
42
|
+
range: {
|
|
43
|
+
from,
|
|
44
|
+
to,
|
|
45
|
+
},
|
|
46
|
+
query: match[0].slice(char.length),
|
|
47
|
+
text: match[0],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
@@ -13,6 +13,10 @@ let filtered_commands = [...commands];
|
|
|
13
13
|
let current_command = filtered_commands.length ? filtered_commands[0] : null;
|
|
14
14
|
let isToolbox = false;
|
|
15
15
|
const dispatch = createEventDispatcher();
|
|
16
|
+
let current_editor_range = void 0;
|
|
17
|
+
export function set_current_editor_range(range) {
|
|
18
|
+
current_editor_range = range;
|
|
19
|
+
}
|
|
16
20
|
let toolboxX;
|
|
17
21
|
let toolboxY;
|
|
18
22
|
export function showAsToolbox(rect) {
|
|
@@ -66,14 +70,14 @@ export function execute_selected_command() {
|
|
|
66
70
|
if (!current_command)
|
|
67
71
|
return;
|
|
68
72
|
hide();
|
|
69
|
-
current_command.on_choice();
|
|
73
|
+
current_command.on_choice(current_editor_range);
|
|
70
74
|
}
|
|
71
75
|
export function filter(key) {
|
|
72
76
|
if (!filtered_commands)
|
|
73
|
-
return;
|
|
77
|
+
return filtered_commands;
|
|
74
78
|
if (!key) {
|
|
75
79
|
filtered_commands = [...commands];
|
|
76
|
-
return;
|
|
80
|
+
return filtered_commands;
|
|
77
81
|
}
|
|
78
82
|
let was_any_items_before = filtered_commands.length > 0;
|
|
79
83
|
filtered_commands = [];
|
|
@@ -91,6 +95,10 @@ export function filter(key) {
|
|
|
91
95
|
}
|
|
92
96
|
if (!was_any_items_before && filtered_commands.length == 0)
|
|
93
97
|
hide();
|
|
98
|
+
return filtered_commands;
|
|
99
|
+
}
|
|
100
|
+
export function get_filtered_commands() {
|
|
101
|
+
return filtered_commands;
|
|
94
102
|
}
|
|
95
103
|
export function navigate_up() {
|
|
96
104
|
if (!current_command) {
|
|
@@ -117,7 +125,7 @@ async function execute_mouse_click(on_choice) {
|
|
|
117
125
|
return;
|
|
118
126
|
dispatch("palette_mouse_click");
|
|
119
127
|
hide();
|
|
120
|
-
on_choice();
|
|
128
|
+
on_choice(current_editor_range);
|
|
121
129
|
}
|
|
122
130
|
function on_mouse_over(cmd) {
|
|
123
131
|
current_command = cmd;
|
|
@@ -5,12 +5,14 @@ declare const __propDef: {
|
|
|
5
5
|
commands: Document_command[];
|
|
6
6
|
width_px?: number | undefined;
|
|
7
7
|
max_height_px?: number | undefined;
|
|
8
|
+
set_current_editor_range?: ((range: any) => void) | undefined;
|
|
8
9
|
showAsToolbox?: ((rect: DOMRect) => void) | undefined;
|
|
9
10
|
show?: ((x: number, y: number, up?: boolean) => void) | undefined;
|
|
10
11
|
show_fullscreen?: ((_width_px: number, _height_px: number) => void) | undefined;
|
|
11
12
|
hide?: (() => void) | undefined;
|
|
12
13
|
execute_selected_command?: (() => void) | undefined;
|
|
13
|
-
filter?: ((key: string) =>
|
|
14
|
+
filter?: ((key: string) => Document_command[]) | undefined;
|
|
15
|
+
get_filtered_commands?: (() => Document_command[]) | undefined;
|
|
14
16
|
navigate_up?: (() => void) | undefined;
|
|
15
17
|
navigate_down?: (() => void) | undefined;
|
|
16
18
|
};
|
|
@@ -27,12 +29,14 @@ export type PaletteProps = typeof __propDef.props;
|
|
|
27
29
|
export type PaletteEvents = typeof __propDef.events;
|
|
28
30
|
export type PaletteSlots = typeof __propDef.slots;
|
|
29
31
|
export default class Palette extends SvelteComponentTyped<PaletteProps, PaletteEvents, PaletteSlots> {
|
|
32
|
+
get set_current_editor_range(): (range: any) => void;
|
|
30
33
|
get showAsToolbox(): (rect: DOMRect) => void;
|
|
31
34
|
get show(): (x: number, y: number, up?: boolean) => void;
|
|
32
35
|
get show_fullscreen(): (_width_px: number, _height_px: number) => void;
|
|
33
36
|
get hide(): () => void;
|
|
34
37
|
get execute_selected_command(): () => void;
|
|
35
|
-
get filter(): (key: string) =>
|
|
38
|
+
get filter(): (key: string) => Document_command[];
|
|
39
|
+
get get_filtered_commands(): () => Document_command[];
|
|
36
40
|
get navigate_up(): () => void;
|
|
37
41
|
get navigate_down(): () => void;
|
|
38
42
|
get commands(): Document_command[];
|
|
@@ -68,5 +72,11 @@ export default class Palette extends SvelteComponentTyped<PaletteProps, PaletteE
|
|
|
68
72
|
get undefined(): any;
|
|
69
73
|
/**accessor*/
|
|
70
74
|
set undefined(_: any);
|
|
75
|
+
get undefined(): any;
|
|
76
|
+
/**accessor*/
|
|
77
|
+
set undefined(_: any);
|
|
78
|
+
get undefined(): any;
|
|
79
|
+
/**accessor*/
|
|
80
|
+
set undefined(_: any);
|
|
71
81
|
}
|
|
72
82
|
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Editor, type Range } from '@tiptap/core';
|
|
2
|
+
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state';
|
|
3
|
+
import { EditorView } from '@tiptap/pm/view';
|
|
4
|
+
import { findSuggestionMatch as defaultFindSuggestionMatch } from './findSuggestionMatch';
|
|
5
|
+
export interface SuggestionOptions<I = any, TSelected = any> {
|
|
6
|
+
/**
|
|
7
|
+
* The plugin key for the suggestion plugin.
|
|
8
|
+
* @default 'suggestion'
|
|
9
|
+
* @example 'mention'
|
|
10
|
+
*/
|
|
11
|
+
pluginKey?: PluginKey;
|
|
12
|
+
/**
|
|
13
|
+
* The editor instance.
|
|
14
|
+
* @default null
|
|
15
|
+
*/
|
|
16
|
+
editor: Editor;
|
|
17
|
+
/**
|
|
18
|
+
* The character that triggers the suggestion.
|
|
19
|
+
* @default '@'
|
|
20
|
+
* @example '#'
|
|
21
|
+
*/
|
|
22
|
+
char?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Allow spaces in the suggestion query. Not compatible with `allowToIncludeChar`. Will be disabled if `allowToIncludeChar` is set to `true`.
|
|
25
|
+
* @default false
|
|
26
|
+
* @example true
|
|
27
|
+
*/
|
|
28
|
+
allowSpaces?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Allow the character to be included in the suggestion query. Not compatible with `allowSpaces`.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
allowToIncludeChar?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Allow prefixes in the suggestion query.
|
|
36
|
+
* @default [' ']
|
|
37
|
+
* @example [' ', '@']
|
|
38
|
+
*/
|
|
39
|
+
allowedPrefixes?: string[] | null;
|
|
40
|
+
/**
|
|
41
|
+
* Only match suggestions at the start of the line.
|
|
42
|
+
* @default false
|
|
43
|
+
* @example true
|
|
44
|
+
*/
|
|
45
|
+
startOfLine?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The tag name of the decoration node.
|
|
48
|
+
* @default 'span'
|
|
49
|
+
* @example 'div'
|
|
50
|
+
*/
|
|
51
|
+
decorationTag?: string;
|
|
52
|
+
/**
|
|
53
|
+
* The class name of the decoration node.
|
|
54
|
+
* @default 'suggestion'
|
|
55
|
+
* @example 'mention'
|
|
56
|
+
*/
|
|
57
|
+
decorationClass?: string;
|
|
58
|
+
/**
|
|
59
|
+
* A function that is called when a suggestion is selected.
|
|
60
|
+
* @param props The props object.
|
|
61
|
+
* @param props.editor The editor instance.
|
|
62
|
+
* @param props.range The range of the suggestion.
|
|
63
|
+
* @param props.props The props of the selected suggestion.
|
|
64
|
+
* @returns void
|
|
65
|
+
* @example ({ editor, range, props }) => { props.command(props.props) }
|
|
66
|
+
*/
|
|
67
|
+
command?: (props: {
|
|
68
|
+
editor: Editor;
|
|
69
|
+
range: Range;
|
|
70
|
+
props: TSelected;
|
|
71
|
+
}) => void;
|
|
72
|
+
/**
|
|
73
|
+
* A function that returns the suggestion items in form of an array.
|
|
74
|
+
* @param props The props object.
|
|
75
|
+
* @param props.editor The editor instance.
|
|
76
|
+
* @param props.query The current suggestion query.
|
|
77
|
+
* @returns An array of suggestion items.
|
|
78
|
+
* @example ({ editor, query }) => [{ id: 1, label: 'John Doe' }]
|
|
79
|
+
*/
|
|
80
|
+
items?: (props: {
|
|
81
|
+
query: string;
|
|
82
|
+
editor: Editor;
|
|
83
|
+
}) => I[] | Promise<I[]>;
|
|
84
|
+
/**
|
|
85
|
+
* The render function for the suggestion.
|
|
86
|
+
* @returns An object with render functions.
|
|
87
|
+
*/
|
|
88
|
+
render?: () => {
|
|
89
|
+
onBeforeStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
90
|
+
onStart?: (props: SuggestionProps<I, TSelected>) => void;
|
|
91
|
+
onBeforeUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
92
|
+
onUpdate?: (props: SuggestionProps<I, TSelected>) => void;
|
|
93
|
+
onExit?: (props: SuggestionProps<I, TSelected>) => void;
|
|
94
|
+
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* A function that returns a boolean to indicate if the suggestion should be active.
|
|
98
|
+
* @param props The props object.
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
allow?: (props: {
|
|
102
|
+
editor: Editor;
|
|
103
|
+
state: EditorState;
|
|
104
|
+
range: Range;
|
|
105
|
+
isActive?: boolean;
|
|
106
|
+
}) => boolean;
|
|
107
|
+
findSuggestionMatch?: typeof defaultFindSuggestionMatch;
|
|
108
|
+
}
|
|
109
|
+
export interface SuggestionProps<I = any, TSelected = any> {
|
|
110
|
+
/**
|
|
111
|
+
* The editor instance.
|
|
112
|
+
*/
|
|
113
|
+
editor: Editor;
|
|
114
|
+
/**
|
|
115
|
+
* The range of the suggestion.
|
|
116
|
+
*/
|
|
117
|
+
range: Range;
|
|
118
|
+
/**
|
|
119
|
+
* The current suggestion query.
|
|
120
|
+
*/
|
|
121
|
+
query: string;
|
|
122
|
+
/**
|
|
123
|
+
* The current suggestion text.
|
|
124
|
+
*/
|
|
125
|
+
text: string;
|
|
126
|
+
/**
|
|
127
|
+
* The suggestion items array.
|
|
128
|
+
*/
|
|
129
|
+
items: I[];
|
|
130
|
+
/**
|
|
131
|
+
* A function that is called when a suggestion is selected.
|
|
132
|
+
* @param props The props object.
|
|
133
|
+
* @returns void
|
|
134
|
+
*/
|
|
135
|
+
command: (props: TSelected) => void;
|
|
136
|
+
/**
|
|
137
|
+
* The decoration node HTML element
|
|
138
|
+
* @default null
|
|
139
|
+
*/
|
|
140
|
+
decorationNode: Element | null;
|
|
141
|
+
/**
|
|
142
|
+
* The function that returns the client rect
|
|
143
|
+
* @default null
|
|
144
|
+
* @example () => new DOMRect(0, 0, 0, 0)
|
|
145
|
+
*/
|
|
146
|
+
clientRect?: (() => DOMRect | null) | null;
|
|
147
|
+
}
|
|
148
|
+
export interface SuggestionKeyDownProps {
|
|
149
|
+
view: EditorView;
|
|
150
|
+
event: KeyboardEvent;
|
|
151
|
+
range: Range;
|
|
152
|
+
}
|
|
153
|
+
export declare const SuggestionPluginKey: PluginKey<any>;
|
|
154
|
+
/**
|
|
155
|
+
* This utility allows you to create suggestions.
|
|
156
|
+
* @see https://tiptap.dev/api/utilities/suggestion
|
|
157
|
+
*/
|
|
158
|
+
export declare function Suggestion<I = any, TSelected = any>({ pluginKey, editor, char, allowSpaces, allowToIncludeChar, allowedPrefixes, startOfLine, decorationTag, decorationClass, command, items, render, allow, findSuggestionMatch, }: SuggestionOptions<I, TSelected>): Plugin<any>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state';
|
|
3
|
+
import { Decoration, DecorationSet, EditorView } from '@tiptap/pm/view';
|
|
4
|
+
import { findSuggestionMatch as defaultFindSuggestionMatch } from './findSuggestionMatch';
|
|
5
|
+
export const SuggestionPluginKey = new PluginKey('suggestion');
|
|
6
|
+
/**
|
|
7
|
+
* This utility allows you to create suggestions.
|
|
8
|
+
* @see https://tiptap.dev/api/utilities/suggestion
|
|
9
|
+
*/
|
|
10
|
+
export function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allowSpaces = false, allowToIncludeChar = false, allowedPrefixes = [' '], startOfLine = false, decorationTag = 'span', decorationClass = 'suggestion', command = () => null, items = () => [], render = () => ({}), allow = () => true, findSuggestionMatch = defaultFindSuggestionMatch, }) {
|
|
11
|
+
let props;
|
|
12
|
+
const renderer = render?.();
|
|
13
|
+
const plugin = new Plugin({
|
|
14
|
+
key: pluginKey,
|
|
15
|
+
view() {
|
|
16
|
+
return {
|
|
17
|
+
update: async (view, prevState) => {
|
|
18
|
+
const prev = this.key?.getState(prevState);
|
|
19
|
+
const next = this.key?.getState(view.state);
|
|
20
|
+
// See how the state changed
|
|
21
|
+
const moved = prev.active && next.active && prev.range.from !== next.range.from;
|
|
22
|
+
const started = !prev.active && next.active;
|
|
23
|
+
const stopped = prev.active && !next.active;
|
|
24
|
+
const changed = !started && !stopped && prev.query !== next.query;
|
|
25
|
+
const handleStart = started || (moved && changed);
|
|
26
|
+
const handleChange = changed || moved;
|
|
27
|
+
const handleExit = stopped || (moved && changed);
|
|
28
|
+
// Cancel when suggestion isn't active
|
|
29
|
+
if (!handleStart && !handleChange && !handleExit) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const state = handleExit && !handleStart ? prev : next;
|
|
33
|
+
const decorationNode = view.dom.querySelector(`[data-decoration-id="${state.decorationId}"]`);
|
|
34
|
+
props = {
|
|
35
|
+
editor,
|
|
36
|
+
range: state.range,
|
|
37
|
+
query: state.query,
|
|
38
|
+
text: state.text,
|
|
39
|
+
items: [],
|
|
40
|
+
command: commandProps => {
|
|
41
|
+
return command({
|
|
42
|
+
editor,
|
|
43
|
+
range: state.range,
|
|
44
|
+
props: commandProps,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
decorationNode,
|
|
48
|
+
// virtual node for popper.js or tippy.js
|
|
49
|
+
// this can be used for building popups without a DOM node
|
|
50
|
+
clientRect: decorationNode
|
|
51
|
+
? () => {
|
|
52
|
+
// because of `items` can be asynchrounous we’ll search for the current decoration node
|
|
53
|
+
const { decorationId } = this.key?.getState(editor.state); // eslint-disable-line
|
|
54
|
+
const currentDecorationNode = view.dom.querySelector(`[data-decoration-id="${decorationId}"]`);
|
|
55
|
+
return currentDecorationNode?.getBoundingClientRect() || null;
|
|
56
|
+
}
|
|
57
|
+
: null,
|
|
58
|
+
};
|
|
59
|
+
if (handleStart) {
|
|
60
|
+
renderer?.onBeforeStart?.(props);
|
|
61
|
+
}
|
|
62
|
+
if (handleChange) {
|
|
63
|
+
renderer?.onBeforeUpdate?.(props);
|
|
64
|
+
}
|
|
65
|
+
if (handleChange || handleStart) {
|
|
66
|
+
props.items = await items({
|
|
67
|
+
editor,
|
|
68
|
+
query: state.query,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (handleExit) {
|
|
72
|
+
//console.log('plugin onExit handleExit')
|
|
73
|
+
renderer?.onExit?.(props);
|
|
74
|
+
}
|
|
75
|
+
if (handleChange) {
|
|
76
|
+
renderer?.onUpdate?.(props);
|
|
77
|
+
}
|
|
78
|
+
if (handleStart) {
|
|
79
|
+
renderer?.onStart?.(props);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
destroy: () => {
|
|
83
|
+
if (!props) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
//console.log('plugin onExit destroy')
|
|
87
|
+
renderer?.onExit?.(props);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
state: {
|
|
92
|
+
// Initialize the plugin's internal state.
|
|
93
|
+
init() {
|
|
94
|
+
const state = {
|
|
95
|
+
active: false,
|
|
96
|
+
range: {
|
|
97
|
+
from: 0,
|
|
98
|
+
to: 0,
|
|
99
|
+
},
|
|
100
|
+
query: null,
|
|
101
|
+
text: null,
|
|
102
|
+
composing: false,
|
|
103
|
+
};
|
|
104
|
+
return state;
|
|
105
|
+
},
|
|
106
|
+
// Apply changes to the plugin state from a view transaction.
|
|
107
|
+
apply(transaction, prev, _oldState, state) {
|
|
108
|
+
const { isEditable } = editor;
|
|
109
|
+
const { composing } = editor.view;
|
|
110
|
+
const { selection } = transaction;
|
|
111
|
+
const { empty, from } = selection;
|
|
112
|
+
const next = { ...prev };
|
|
113
|
+
let blockPluginActivation = false;
|
|
114
|
+
if (!prev.active) {
|
|
115
|
+
blockPluginActivation = true;
|
|
116
|
+
if (transaction && transaction.steps.length > 0) {
|
|
117
|
+
const modStep = transaction.steps.find(s => s.jsonID === 'replace');
|
|
118
|
+
if (modStep)
|
|
119
|
+
blockPluginActivation = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
next.composing = composing;
|
|
123
|
+
// We can only be suggesting if the view is editable, and:
|
|
124
|
+
// * there is no selection, or
|
|
125
|
+
// * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
|
|
126
|
+
if (isEditable && (empty || editor.view.composing) && !blockPluginActivation) {
|
|
127
|
+
// Reset active state if we just left the previous suggestion range
|
|
128
|
+
if ((from < prev.range.from || from > prev.range.to) && !composing && !prev.composing) {
|
|
129
|
+
next.active = false;
|
|
130
|
+
}
|
|
131
|
+
// Try to match against where our cursor currently is
|
|
132
|
+
const match = findSuggestionMatch({
|
|
133
|
+
char,
|
|
134
|
+
allowSpaces,
|
|
135
|
+
allowToIncludeChar,
|
|
136
|
+
allowedPrefixes,
|
|
137
|
+
startOfLine,
|
|
138
|
+
$position: selection.$from,
|
|
139
|
+
});
|
|
140
|
+
const decorationId = `id_${Math.floor(Math.random() * 0xffffffff)}`;
|
|
141
|
+
// If we found a match, update the current state to show it
|
|
142
|
+
if (match && allow({
|
|
143
|
+
editor, state, range: match.range, isActive: prev.active,
|
|
144
|
+
})) {
|
|
145
|
+
next.active = true;
|
|
146
|
+
next.decorationId = prev.decorationId ? prev.decorationId : decorationId;
|
|
147
|
+
next.range = match.range;
|
|
148
|
+
next.query = match.query;
|
|
149
|
+
next.text = match.text;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
next.active = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
next.active = false;
|
|
157
|
+
}
|
|
158
|
+
// Make sure to empty the range if suggestion is inactive
|
|
159
|
+
if (!next.active) {
|
|
160
|
+
next.decorationId = null;
|
|
161
|
+
next.range = { from: 0, to: 0 };
|
|
162
|
+
next.query = null;
|
|
163
|
+
next.text = null;
|
|
164
|
+
}
|
|
165
|
+
return next;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
props: {
|
|
169
|
+
// Call the keydown hook if suggestion is active.
|
|
170
|
+
handleKeyDown(view, event) {
|
|
171
|
+
const { active, range } = plugin.getState(view.state);
|
|
172
|
+
if (!active) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return renderer?.onKeyDown?.({ view, event, range }) || false;
|
|
176
|
+
},
|
|
177
|
+
// Setup decorator on the currently active suggestion.
|
|
178
|
+
decorations(state) {
|
|
179
|
+
const { active, range, decorationId } = plugin.getState(state);
|
|
180
|
+
if (!active) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return DecorationSet.create(state.doc, [
|
|
184
|
+
Decoration.inline(range.from, range.to, {
|
|
185
|
+
nodeName: decorationTag,
|
|
186
|
+
class: decorationClass,
|
|
187
|
+
'data-decoration-id': decorationId,
|
|
188
|
+
}),
|
|
189
|
+
]);
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
return plugin;
|
|
194
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { default as Combo } from './components/combo/combo.svelte';
|
|
|
21
21
|
export { default as ComboSource } from './components/combo/combo.source.svelte';
|
|
22
22
|
export { default as ComboItem } from './components/combo/combo.item.svelte';
|
|
23
23
|
export { default as RichEdit } from './components/document/rich.edit.svelte';
|
|
24
|
+
export { default as Editor } from './components/document/editor.svelte';
|
|
24
25
|
export { default as Spinner } from './components/delayed.spinner.svelte';
|
|
25
26
|
export { showMenu, showGridMenu, showFloatingToolbar } from './components/menu';
|
|
26
27
|
export { default as Fab } from './components/Fab.svelte';
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export { default as Combo } from './components/combo/combo.svelte';
|
|
|
26
26
|
export { default as ComboSource } from './components/combo/combo.source.svelte';
|
|
27
27
|
export { default as ComboItem } from './components/combo/combo.item.svelte';
|
|
28
28
|
export { default as RichEdit } from './components/document/rich.edit.svelte';
|
|
29
|
+
export { default as Editor } from './components/document/editor.svelte';
|
|
29
30
|
export { default as Spinner } from './components/delayed.spinner.svelte';
|
|
30
31
|
//export { default as Menu } from './components/contextmenu.svelte'
|
|
31
32
|
export { showMenu, showGridMenu, showFloatingToolbar } from './components/menu';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humandialog/forms.svelte",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Basic Svelte UI components for Object Reef applications",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@playwright/test": "^1.28.1",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@humandialog/auth.svelte": "^1.8.3",
|
|
30
|
+
"@tiptap/core": "^2.11.0",
|
|
31
|
+
"@tiptap/pm": "^2.11.0",
|
|
32
|
+
"@tiptap/starter-kit": "^2.11.0",
|
|
33
|
+
"@tiptap/suggestion": "^2.11.0",
|
|
30
34
|
"flowbite-svelte": "^0.44.4",
|
|
31
35
|
"svelte-icons": "^2.1.0",
|
|
32
36
|
"svelte-spa-router": "^4.0.1"
|
|
@@ -60,7 +64,9 @@
|
|
|
60
64
|
"./components/contextmenu.svelte": "./components/contextmenu.svelte",
|
|
61
65
|
"./components/date.svelte": "./components/date.svelte",
|
|
62
66
|
"./components/delayed.spinner.svelte": "./components/delayed.spinner.svelte",
|
|
67
|
+
"./components/document/editor.svelte": "./components/document/editor.svelte",
|
|
63
68
|
"./components/document/internal/Document_command": "./components/document/internal/Document_command.js",
|
|
69
|
+
"./components/document/internal/findSuggestionMatch": "./components/document/internal/findSuggestionMatch.js",
|
|
64
70
|
"./components/document/internal/h1.icon.svelte": "./components/document/internal/h1.icon.svelte",
|
|
65
71
|
"./components/document/internal/h2.icon.svelte": "./components/document/internal/h2.icon.svelte",
|
|
66
72
|
"./components/document/internal/h3.icon.svelte": "./components/document/internal/h3.icon.svelte",
|
|
@@ -69,6 +75,7 @@
|
|
|
69
75
|
"./components/document/internal/palette.svelte": "./components/document/internal/palette.svelte",
|
|
70
76
|
"./components/document/internal/Selection_helper": "./components/document/internal/Selection_helper.js",
|
|
71
77
|
"./components/document/internal/Selection_range": "./components/document/internal/Selection_range.js",
|
|
78
|
+
"./components/document/internal/suggestion": "./components/document/internal/suggestion.js",
|
|
72
79
|
"./components/document/rich.edit.svelte": "./components/document/rich.edit.svelte",
|
|
73
80
|
"./components/edit.field.svelte": "./components/edit.field.svelte",
|
|
74
81
|
"./components/Fab.svelte": "./components/Fab.svelte",
|