@humandialog/forms.svelte 1.1.6 → 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.
@@ -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) => void) | undefined;
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) => void;
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.6",
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",
@@ -22,7 +22,7 @@
22
22
  import { reef, session, signInHRef } from '@humandialog/auth.svelte';
23
23
  import { ComboSource } from './';
24
24
  import {showMenu} from './components/menu'
25
- import {onErroronErrorAddAlert} from './stores'
25
+ import {onErrorShowAlert} from './stores'
26
26
 
27
27
 
28
28
  // ==============================================================================