@spatika/editor 1.5.0

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.
Files changed (142) hide show
  1. package/README.md +43 -0
  2. package/dist/composites/AiCommandDock.d.ts +13 -0
  3. package/dist/composites/AiCommandDock.d.ts.map +1 -0
  4. package/dist/composites/AiCommandDock.js +63 -0
  5. package/dist/composites/AiCommandMenu.d.ts +16 -0
  6. package/dist/composites/AiCommandMenu.d.ts.map +1 -0
  7. package/dist/composites/AiCommandMenu.js +51 -0
  8. package/dist/composites/AiCommandPanel.d.ts +12 -0
  9. package/dist/composites/AiCommandPanel.d.ts.map +1 -0
  10. package/dist/composites/AiCommandPanel.js +12 -0
  11. package/dist/composites/AiOverlay.d.ts +7 -0
  12. package/dist/composites/AiOverlay.d.ts.map +1 -0
  13. package/dist/composites/AiOverlay.js +8 -0
  14. package/dist/composites/EditorBubbleMenu.d.ts +10 -0
  15. package/dist/composites/EditorBubbleMenu.d.ts.map +1 -0
  16. package/dist/composites/EditorBubbleMenu.js +16 -0
  17. package/dist/composites/EditorToolbar.d.ts +20 -0
  18. package/dist/composites/EditorToolbar.d.ts.map +1 -0
  19. package/dist/composites/EditorToolbar.js +45 -0
  20. package/dist/composites/ImageBubbleMenu.d.ts +7 -0
  21. package/dist/composites/ImageBubbleMenu.d.ts.map +1 -0
  22. package/dist/composites/ImageBubbleMenu.js +15 -0
  23. package/dist/composites/MentionMenu.d.ts +11 -0
  24. package/dist/composites/MentionMenu.d.ts.map +1 -0
  25. package/dist/composites/MentionMenu.js +10 -0
  26. package/dist/composites/SlashCommandMenu.d.ts +13 -0
  27. package/dist/composites/SlashCommandMenu.d.ts.map +1 -0
  28. package/dist/composites/SlashCommandMenu.js +10 -0
  29. package/dist/composites/SpatikaEditor.d.ts +14 -0
  30. package/dist/composites/SpatikaEditor.d.ts.map +1 -0
  31. package/dist/composites/SpatikaEditor.js +159 -0
  32. package/dist/composites/TableGridPicker.d.ts +9 -0
  33. package/dist/composites/TableGridPicker.d.ts.map +1 -0
  34. package/dist/composites/TableGridPicker.js +54 -0
  35. package/dist/composites/ToolbarButton.d.ts +7 -0
  36. package/dist/composites/ToolbarButton.d.ts.map +1 -0
  37. package/dist/composites/ToolbarButton.js +7 -0
  38. package/dist/composites/ToolbarFormatPanels.d.ts +9 -0
  39. package/dist/composites/ToolbarFormatPanels.d.ts.map +1 -0
  40. package/dist/composites/ToolbarFormatPanels.js +35 -0
  41. package/dist/composites/ToolbarInsertPanels.d.ts +14 -0
  42. package/dist/composites/ToolbarInsertPanels.d.ts.map +1 -0
  43. package/dist/composites/ToolbarInsertPanels.js +96 -0
  44. package/dist/composites/ToolbarPopover.d.ts +14 -0
  45. package/dist/composites/ToolbarPopover.d.ts.map +1 -0
  46. package/dist/composites/ToolbarPopover.js +48 -0
  47. package/dist/extensions/ai-action.d.ts +24 -0
  48. package/dist/extensions/ai-action.d.ts.map +1 -0
  49. package/dist/extensions/ai-action.js +53 -0
  50. package/dist/extensions/mention.d.ts +17 -0
  51. package/dist/extensions/mention.d.ts.map +1 -0
  52. package/dist/extensions/mention.js +129 -0
  53. package/dist/extensions/resizable-image-view.d.ts +18 -0
  54. package/dist/extensions/resizable-image-view.d.ts.map +1 -0
  55. package/dist/extensions/resizable-image-view.js +70 -0
  56. package/dist/extensions/resizable-image.d.ts +4 -0
  57. package/dist/extensions/resizable-image.d.ts.map +1 -0
  58. package/dist/extensions/resizable-image.js +68 -0
  59. package/dist/extensions/slash-command.d.ts +18 -0
  60. package/dist/extensions/slash-command.d.ts.map +1 -0
  61. package/dist/extensions/slash-command.js +178 -0
  62. package/dist/index.d.ts +24 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +18 -0
  65. package/dist/lib/ai-command-menu.d.ts +9 -0
  66. package/dist/lib/ai-command-menu.d.ts.map +1 -0
  67. package/dist/lib/ai-command-menu.js +43 -0
  68. package/dist/lib/cn.d.ts +4 -0
  69. package/dist/lib/cn.d.ts.map +1 -0
  70. package/dist/lib/cn.js +6 -0
  71. package/dist/lib/editor-format-colors.d.ts +10 -0
  72. package/dist/lib/editor-format-colors.d.ts.map +1 -0
  73. package/dist/lib/editor-format-colors.js +24 -0
  74. package/dist/lib/image-upload.d.ts +14 -0
  75. package/dist/lib/image-upload.d.ts.map +1 -0
  76. package/dist/lib/image-upload.js +29 -0
  77. package/dist/lib/insert-editor-images.d.ts +5 -0
  78. package/dist/lib/insert-editor-images.d.ts.map +1 -0
  79. package/dist/lib/insert-editor-images.js +32 -0
  80. package/dist/lib/types.d.ts +131 -0
  81. package/dist/lib/types.d.ts.map +1 -0
  82. package/dist/lib/types.js +12 -0
  83. package/dist/lib/use-spatika-editor.d.ts +13 -0
  84. package/dist/lib/use-spatika-editor.d.ts.map +1 -0
  85. package/dist/lib/use-spatika-editor.js +171 -0
  86. package/dist/styles.css +1056 -0
  87. package/package.json +101 -0
  88. package/src/composites/AiCommandDock.test.tsx +48 -0
  89. package/src/composites/AiCommandDock.tsx +137 -0
  90. package/src/composites/AiCommandMenu.test.tsx +67 -0
  91. package/src/composites/AiCommandMenu.tsx +120 -0
  92. package/src/composites/AiCommandPanel.test.tsx +42 -0
  93. package/src/composites/AiCommandPanel.tsx +56 -0
  94. package/src/composites/AiOverlay.test.tsx +18 -0
  95. package/src/composites/AiOverlay.tsx +18 -0
  96. package/src/composites/EditorBubbleMenu.test.tsx +49 -0
  97. package/src/composites/EditorBubbleMenu.tsx +73 -0
  98. package/src/composites/EditorToolbar.test.tsx +146 -0
  99. package/src/composites/EditorToolbar.tsx +362 -0
  100. package/src/composites/ImageBubbleMenu.test.tsx +43 -0
  101. package/src/composites/ImageBubbleMenu.tsx +51 -0
  102. package/src/composites/MentionMenu.test.tsx +25 -0
  103. package/src/composites/MentionMenu.tsx +46 -0
  104. package/src/composites/SlashCommandMenu.test.tsx +33 -0
  105. package/src/composites/SlashCommandMenu.tsx +54 -0
  106. package/src/composites/SpatikaEditor.test.tsx +167 -0
  107. package/src/composites/SpatikaEditor.tsx +302 -0
  108. package/src/composites/TableGridPicker.test.tsx +26 -0
  109. package/src/composites/TableGridPicker.tsx +138 -0
  110. package/src/composites/ToolbarButton.test.tsx +21 -0
  111. package/src/composites/ToolbarButton.tsx +24 -0
  112. package/src/composites/ToolbarFormatPanels.test.tsx +46 -0
  113. package/src/composites/ToolbarFormatPanels.tsx +78 -0
  114. package/src/composites/ToolbarInsertPanels.test.tsx +60 -0
  115. package/src/composites/ToolbarInsertPanels.tsx +200 -0
  116. package/src/composites/ToolbarPopover.test.tsx +24 -0
  117. package/src/composites/ToolbarPopover.tsx +95 -0
  118. package/src/extensions/ai-action.test.ts +26 -0
  119. package/src/extensions/ai-action.ts +73 -0
  120. package/src/extensions/mention.test.ts +31 -0
  121. package/src/extensions/mention.ts +158 -0
  122. package/src/extensions/resizable-image-view.test.ts +26 -0
  123. package/src/extensions/resizable-image-view.ts +96 -0
  124. package/src/extensions/resizable-image.test.ts +28 -0
  125. package/src/extensions/resizable-image.ts +75 -0
  126. package/src/extensions/slash-command.test.ts +45 -0
  127. package/src/extensions/slash-command.ts +197 -0
  128. package/src/index.ts +49 -0
  129. package/src/lib/ai-command-menu.test.ts +48 -0
  130. package/src/lib/ai-command-menu.ts +57 -0
  131. package/src/lib/cn.ts +7 -0
  132. package/src/lib/editor-format-colors.test.ts +15 -0
  133. package/src/lib/editor-format-colors.ts +31 -0
  134. package/src/lib/image-upload.test.ts +19 -0
  135. package/src/lib/image-upload.ts +49 -0
  136. package/src/lib/insert-editor-images.test.ts +76 -0
  137. package/src/lib/insert-editor-images.ts +44 -0
  138. package/src/lib/types.ts +149 -0
  139. package/src/lib/use-spatika-editor.test.tsx +56 -0
  140. package/src/lib/use-spatika-editor.ts +191 -0
  141. package/src/styles/editor.css +1056 -0
  142. package/src/test/setup.ts +46 -0
@@ -0,0 +1,129 @@
1
+ import { Extension, mergeAttributes, Node } from "@tiptap/core";
2
+ import { PluginKey } from "@tiptap/pm/state";
3
+ import Suggestion from "@tiptap/suggestion";
4
+ export const MentionPluginKey = new PluginKey("spatikaMention");
5
+ export function createMentionExtension(contacts, onUpdate) {
6
+ const MentionNode = Node.create({
7
+ name: "mention",
8
+ group: "inline",
9
+ inline: true,
10
+ atom: true,
11
+ selectable: false,
12
+ addAttributes() {
13
+ return {
14
+ id: { default: null },
15
+ label: { default: null },
16
+ };
17
+ },
18
+ parseHTML() {
19
+ return [{ tag: 'span[data-type="mention"]' }];
20
+ },
21
+ renderHTML({ node, HTMLAttributes }) {
22
+ return [
23
+ "span",
24
+ mergeAttributes(HTMLAttributes, {
25
+ "data-type": "mention",
26
+ class: "spk-editor-mention",
27
+ }),
28
+ `@${node.attrs.label ?? node.attrs.id}`,
29
+ ];
30
+ },
31
+ renderText({ node }) {
32
+ return `@${node.attrs.label ?? node.attrs.id}`;
33
+ },
34
+ });
35
+ const suggestionExtension = ExtensionWithSuggestion(contacts, onUpdate);
36
+ return [MentionNode, suggestionExtension];
37
+ }
38
+ function ExtensionWithSuggestion(contacts, onUpdate) {
39
+ return Extension.create({
40
+ name: "mentionSuggestion",
41
+ addStorage() {
42
+ return {
43
+ contacts,
44
+ activeIndex: 0,
45
+ query: "",
46
+ range: null,
47
+ filteredItems: contacts,
48
+ runCommand: null,
49
+ };
50
+ },
51
+ addProseMirrorPlugins() {
52
+ const editor = this.editor;
53
+ const storage = this.storage;
54
+ const suggestion = {
55
+ char: "@",
56
+ pluginKey: MentionPluginKey,
57
+ allowSpaces: false,
58
+ items: ({ query }) => {
59
+ const needle = query.toLowerCase();
60
+ return contacts.filter((contact) => contact.label.toLowerCase().includes(needle));
61
+ },
62
+ command: ({ editor: ed, range, props }) => {
63
+ ed.chain()
64
+ .focus()
65
+ .insertContentAt(range, [
66
+ {
67
+ type: "mention",
68
+ attrs: { id: props.id, label: props.label },
69
+ },
70
+ { type: "text", text: " " },
71
+ ])
72
+ .run();
73
+ },
74
+ render: () => ({
75
+ onStart: (props) => {
76
+ storage.query = props.query;
77
+ storage.activeIndex = 0;
78
+ storage.range = props.range;
79
+ storage.filteredItems = props.items;
80
+ storage.runCommand = (item) => props.command(item);
81
+ onUpdate?.({ ...storage });
82
+ },
83
+ onUpdate: (props) => {
84
+ storage.query = props.query;
85
+ storage.activeIndex = 0;
86
+ storage.range = props.range;
87
+ storage.filteredItems = props.items;
88
+ storage.runCommand = (item) => props.command(item);
89
+ onUpdate?.({ ...storage });
90
+ },
91
+ onKeyDown: (props) => {
92
+ if (props.event.key === "ArrowUp") {
93
+ storage.activeIndex = Math.max(0, storage.activeIndex - 1);
94
+ onUpdate?.({ ...storage });
95
+ return true;
96
+ }
97
+ if (props.event.key === "ArrowDown") {
98
+ storage.activeIndex = Math.min(storage.filteredItems.length - 1, storage.activeIndex + 1);
99
+ onUpdate?.({ ...storage });
100
+ return true;
101
+ }
102
+ if (props.event.key === "Enter") {
103
+ const item = storage.filteredItems[storage.activeIndex];
104
+ if (item)
105
+ storage.runCommand?.(item);
106
+ return true;
107
+ }
108
+ if (props.event.key === "Escape") {
109
+ storage.query = "";
110
+ storage.range = null;
111
+ onUpdate?.({ ...storage });
112
+ return true;
113
+ }
114
+ return false;
115
+ },
116
+ onExit: () => {
117
+ storage.query = "";
118
+ storage.range = null;
119
+ storage.activeIndex = 0;
120
+ storage.filteredItems = contacts;
121
+ storage.runCommand = null;
122
+ onUpdate?.({ ...storage });
123
+ },
124
+ }),
125
+ };
126
+ return [Suggestion({ editor, ...suggestion })];
127
+ },
128
+ });
129
+ }
@@ -0,0 +1,18 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
3
+ type CreateImageNodeViewOptions = {
4
+ node: ProseMirrorNode;
5
+ getPos: () => number | undefined;
6
+ editor: Editor;
7
+ selected: boolean;
8
+ };
9
+ export declare function applyImageWrapState(wrap: HTMLElement, img: HTMLImageElement, node: ProseMirrorNode, selected: boolean): void;
10
+ export declare function createImageNodeView({ node, getPos, editor, selected }: CreateImageNodeViewOptions): {
11
+ dom: HTMLDivElement;
12
+ update(updatedNode: ProseMirrorNode): boolean;
13
+ selectNode(): void;
14
+ deselectNode(): void;
15
+ destroy(): void;
16
+ };
17
+ export {};
18
+ //# sourceMappingURL=resizable-image-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable-image-view.d.ts","sourceRoot":"","sources":["../../src/extensions/resizable-image-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGhE,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,eAAe,EACrB,QAAQ,EAAE,OAAO,QAYlB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,0BAA0B;;wBAgD1E,eAAe;;;;EAkBtC"}
@@ -0,0 +1,70 @@
1
+ export function applyImageWrapState(wrap, img, node, selected) {
2
+ const width = node.attrs.width ?? "100%";
3
+ const align = node.attrs.align ?? "center";
4
+ wrap.dataset.align = align;
5
+ wrap.className = `spk-editor-image-wrap spk-editor-image-wrap--${align}${selected ? " spk-editor-image-wrap--selected" : ""}`;
6
+ img.src = node.attrs.src;
7
+ img.alt = node.attrs.alt ?? "";
8
+ img.className = "spk-editor-image";
9
+ img.style.width = width;
10
+ img.draggable = false;
11
+ }
12
+ export function createImageNodeView({ node, getPos, editor, selected }) {
13
+ const wrap = document.createElement("div");
14
+ wrap.contentEditable = "false";
15
+ wrap.className = "spk-editor-image-wrap";
16
+ const img = document.createElement("img");
17
+ const handle = document.createElement("span");
18
+ handle.className = "spk-editor-image-resize-handle";
19
+ handle.setAttribute("aria-hidden", "true");
20
+ handle.title = "Drag to resize";
21
+ applyImageWrapState(wrap, img, node, selected);
22
+ wrap.append(img, handle);
23
+ let startX = 0;
24
+ let startWidth = 0;
25
+ const onPointerMove = (event) => {
26
+ const pos = getPos();
27
+ if (pos === undefined)
28
+ return;
29
+ const delta = event.clientX - startX;
30
+ const nextWidth = Math.max(120, Math.round(startWidth + delta));
31
+ editor.view.dispatch(editor.view.state.tr.setNodeMarkup(pos, undefined, {
32
+ ...node.attrs,
33
+ width: `${nextWidth}px`,
34
+ }));
35
+ };
36
+ const onPointerUp = () => {
37
+ document.removeEventListener("pointermove", onPointerMove);
38
+ document.removeEventListener("pointerup", onPointerUp);
39
+ };
40
+ const onPointerDown = (event) => {
41
+ event.preventDefault();
42
+ event.stopPropagation();
43
+ startX = event.clientX;
44
+ startWidth = img.getBoundingClientRect().width;
45
+ document.addEventListener("pointermove", onPointerMove);
46
+ document.addEventListener("pointerup", onPointerUp);
47
+ };
48
+ handle.addEventListener("pointerdown", onPointerDown);
49
+ return {
50
+ dom: wrap,
51
+ update(updatedNode) {
52
+ if (updatedNode.type.name !== "image")
53
+ return false;
54
+ node = updatedNode;
55
+ applyImageWrapState(wrap, img, updatedNode, selected);
56
+ return true;
57
+ },
58
+ selectNode() {
59
+ wrap.classList.add("spk-editor-image-wrap--selected");
60
+ },
61
+ deselectNode() {
62
+ wrap.classList.remove("spk-editor-image-wrap--selected");
63
+ },
64
+ destroy() {
65
+ handle.removeEventListener("pointerdown", onPointerDown);
66
+ document.removeEventListener("pointermove", onPointerMove);
67
+ document.removeEventListener("pointerup", onPointerUp);
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,4 @@
1
+ export type ImageAlign = "left" | "center" | "right";
2
+ /** Image node with drag-resize handles, width, and float alignment attributes. */
3
+ export declare const ResizableImage: import("@tiptap/core").Node<import("@tiptap/extension-image").ImageOptions, any>;
4
+ //# sourceMappingURL=resizable-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable-image.d.ts","sourceRoot":"","sources":["../../src/extensions/resizable-image.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD,kFAAkF;AAClF,eAAO,MAAM,cAAc,kFAmEzB,CAAC"}
@@ -0,0 +1,68 @@
1
+ import Image from "@tiptap/extension-image";
2
+ import { mergeAttributes } from "@tiptap/core";
3
+ import { createImageNodeView } from "./resizable-image-view";
4
+ /** Image node with drag-resize handles, width, and float alignment attributes. */
5
+ export const ResizableImage = Image.extend({
6
+ name: "image",
7
+ addAttributes() {
8
+ return {
9
+ ...this.parent?.(),
10
+ width: {
11
+ default: "100%",
12
+ parseHTML: (element) => {
13
+ const fromData = element.getAttribute("data-width");
14
+ if (fromData)
15
+ return fromData;
16
+ const styleWidth = element.style.width;
17
+ return styleWidth || "100%";
18
+ },
19
+ renderHTML: (attributes) => ({
20
+ "data-width": attributes.width,
21
+ style: `width: ${attributes.width}`,
22
+ }),
23
+ },
24
+ align: {
25
+ default: "center",
26
+ parseHTML: (element) => {
27
+ const parent = element.parentElement;
28
+ const fromParent = parent?.getAttribute("data-align");
29
+ return fromParent ?? (element.getAttribute("data-align") ?? "center");
30
+ },
31
+ renderHTML: (attributes) => ({
32
+ "data-align": attributes.align,
33
+ class: `spk-editor-image spk-editor-image--${attributes.align}`,
34
+ }),
35
+ },
36
+ };
37
+ },
38
+ addNodeView() {
39
+ return ({ node, getPos, editor }) => createImageNodeView({
40
+ node,
41
+ getPos,
42
+ editor,
43
+ selected: false,
44
+ });
45
+ },
46
+ renderHTML({ HTMLAttributes }) {
47
+ const align = HTMLAttributes["data-align"] ?? "center";
48
+ return [
49
+ "div",
50
+ {
51
+ class: `spk-editor-image-wrap spk-editor-image-wrap--${align}`,
52
+ "data-align": align,
53
+ contenteditable: "false",
54
+ },
55
+ ["img", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)],
56
+ ];
57
+ },
58
+ parseHTML() {
59
+ return [
60
+ {
61
+ tag: "div.spk-editor-image-wrap img",
62
+ },
63
+ {
64
+ tag: "img[src]",
65
+ },
66
+ ];
67
+ },
68
+ });
@@ -0,0 +1,18 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { PluginKey } from "@tiptap/pm/state";
3
+ import type { SlashCommandItem } from "../lib/types";
4
+ export type SlashCommandStorage = {
5
+ items: SlashCommandItem[];
6
+ activeIndex: number;
7
+ query: string;
8
+ range: {
9
+ from: number;
10
+ to: number;
11
+ } | null;
12
+ filteredItems: SlashCommandItem[];
13
+ runCommand: ((item: SlashCommandItem) => void) | null;
14
+ };
15
+ export declare const SlashCommandPluginKey: PluginKey<any>;
16
+ export declare function createSlashCommandExtension(items: SlashCommandItem[], onUpdate?: (state: SlashCommandStorage) => void): Extension<any, any>;
17
+ export declare function defaultSlashCommands(): SlashCommandItem[];
18
+ //# sourceMappingURL=slash-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../../src/extensions/slash-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3C,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,eAAO,MAAM,qBAAqB,gBAAuC,CAAC;AAE1E,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gBAAgB,EAAE,EACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,uBA+FhD;AAED,wBAAgB,oBAAoB,IAAI,gBAAgB,EAAE,CAiFzD"}
@@ -0,0 +1,178 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { PluginKey } from "@tiptap/pm/state";
3
+ import Suggestion from "@tiptap/suggestion";
4
+ export const SlashCommandPluginKey = new PluginKey("spatikaSlashCommand");
5
+ export function createSlashCommandExtension(items, onUpdate) {
6
+ return Extension.create({
7
+ name: "slashCommand",
8
+ addStorage() {
9
+ return {
10
+ items,
11
+ activeIndex: 0,
12
+ query: "",
13
+ range: null,
14
+ filteredItems: items,
15
+ runCommand: null,
16
+ };
17
+ },
18
+ addProseMirrorPlugins() {
19
+ const editor = this.editor;
20
+ const storage = this.storage;
21
+ const suggestion = {
22
+ char: "/",
23
+ pluginKey: SlashCommandPluginKey,
24
+ allowSpaces: true,
25
+ items: ({ query }) => {
26
+ const needle = query.toLowerCase();
27
+ return items.filter((item) => {
28
+ if (!needle)
29
+ return true;
30
+ const haystack = [item.title, item.description, ...(item.keywords ?? [])]
31
+ .filter(Boolean)
32
+ .join(" ")
33
+ .toLowerCase();
34
+ return haystack.includes(needle);
35
+ });
36
+ },
37
+ command: ({ editor: ed, range, props }) => {
38
+ ed.chain().focus().deleteRange(range).run();
39
+ props.command(editor);
40
+ },
41
+ render: () => ({
42
+ onStart: (props) => {
43
+ storage.query = props.query;
44
+ storage.activeIndex = 0;
45
+ storage.range = props.range;
46
+ storage.filteredItems = props.items;
47
+ storage.runCommand = (item) => props.command(item);
48
+ onUpdate?.({ ...storage });
49
+ },
50
+ onUpdate: (props) => {
51
+ storage.query = props.query;
52
+ storage.activeIndex = 0;
53
+ storage.range = props.range;
54
+ storage.filteredItems = props.items;
55
+ storage.runCommand = (item) => props.command(item);
56
+ onUpdate?.({ ...storage });
57
+ },
58
+ onKeyDown: (props) => {
59
+ if (props.event.key === "ArrowUp") {
60
+ storage.activeIndex = Math.max(0, storage.activeIndex - 1);
61
+ onUpdate?.({ ...storage });
62
+ return true;
63
+ }
64
+ if (props.event.key === "ArrowDown") {
65
+ const max = storage.filteredItems.length - 1;
66
+ storage.activeIndex = Math.min(max, storage.activeIndex + 1);
67
+ onUpdate?.({ ...storage });
68
+ return true;
69
+ }
70
+ if (props.event.key === "Enter") {
71
+ const item = storage.filteredItems[storage.activeIndex];
72
+ if (item)
73
+ storage.runCommand?.(item);
74
+ return true;
75
+ }
76
+ if (props.event.key === "Escape") {
77
+ storage.query = "";
78
+ storage.range = null;
79
+ onUpdate?.({ ...storage });
80
+ return true;
81
+ }
82
+ return false;
83
+ },
84
+ onExit: () => {
85
+ storage.query = "";
86
+ storage.range = null;
87
+ storage.activeIndex = 0;
88
+ storage.filteredItems = items;
89
+ storage.runCommand = null;
90
+ onUpdate?.({ ...storage });
91
+ },
92
+ }),
93
+ };
94
+ return [Suggestion({ editor, ...suggestion })];
95
+ },
96
+ });
97
+ }
98
+ export function defaultSlashCommands() {
99
+ return [
100
+ {
101
+ id: "paragraph",
102
+ title: "Text",
103
+ description: "Plain paragraph",
104
+ keywords: ["p", "text"],
105
+ command: (editor) => editor.chain().focus().setParagraph().run(),
106
+ },
107
+ {
108
+ id: "h1",
109
+ title: "Heading 1",
110
+ description: "Large section title",
111
+ keywords: ["heading", "title"],
112
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 1 }).run(),
113
+ },
114
+ {
115
+ id: "h2",
116
+ title: "Heading 2",
117
+ description: "Medium section title",
118
+ keywords: ["heading", "subtitle"],
119
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 2 }).run(),
120
+ },
121
+ {
122
+ id: "h3",
123
+ title: "Heading 3",
124
+ description: "Small section title",
125
+ keywords: ["heading"],
126
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 3 }).run(),
127
+ },
128
+ {
129
+ id: "bullet",
130
+ title: "Bullet list",
131
+ description: "Unordered list",
132
+ keywords: ["ul", "list"],
133
+ command: (editor) => editor.chain().focus().toggleBulletList().run(),
134
+ },
135
+ {
136
+ id: "ordered",
137
+ title: "Numbered list",
138
+ description: "Ordered list",
139
+ keywords: ["ol", "list"],
140
+ command: (editor) => editor.chain().focus().toggleOrderedList().run(),
141
+ },
142
+ {
143
+ id: "task",
144
+ title: "Task list",
145
+ description: "Checklist with todos",
146
+ keywords: ["todo", "checkbox", "task"],
147
+ command: (editor) => editor.chain().focus().toggleTaskList().run(),
148
+ },
149
+ {
150
+ id: "code-block",
151
+ title: "Code block",
152
+ description: "Monospace code snippet",
153
+ keywords: ["code", "pre", "snippet"],
154
+ command: (editor) => editor.chain().focus().toggleCodeBlock().run(),
155
+ },
156
+ {
157
+ id: "quote",
158
+ title: "Quote",
159
+ description: "Block quotation",
160
+ keywords: ["blockquote"],
161
+ command: (editor) => editor.chain().focus().toggleBlockquote().run(),
162
+ },
163
+ {
164
+ id: "divider",
165
+ title: "Divider",
166
+ description: "Horizontal rule",
167
+ keywords: ["hr", "line"],
168
+ command: (editor) => editor.chain().focus().setHorizontalRule().run(),
169
+ },
170
+ {
171
+ id: "table",
172
+ title: "Table",
173
+ description: "3×3 table with header",
174
+ keywords: ["grid"],
175
+ command: (editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
176
+ },
177
+ ];
178
+ }
@@ -0,0 +1,24 @@
1
+ export { SpatikaEditor } from "./composites/SpatikaEditor";
2
+ export { EditorToolbar } from "./composites/EditorToolbar";
3
+ export { EditorBubbleMenu } from "./composites/EditorBubbleMenu";
4
+ export { ImageBubbleMenu } from "./composites/ImageBubbleMenu";
5
+ export { SlashCommandMenu } from "./composites/SlashCommandMenu";
6
+ export { MentionMenu } from "./composites/MentionMenu";
7
+ export { AiCommandMenu } from "./composites/AiCommandMenu";
8
+ export { AiCommandDock } from "./composites/AiCommandDock";
9
+ export { AiCommandPanel } from "./composites/AiCommandPanel";
10
+ export type { AiCommandRunRequest } from "./composites/AiCommandMenu";
11
+ export { AiOverlay } from "./composites/AiOverlay";
12
+ export { ToolbarButton } from "./composites/ToolbarButton";
13
+ export { useSpatikaEditor, buildSpatikaExtensions } from "./lib/use-spatika-editor";
14
+ export { DEFAULT_AI_ACTIONS, DEFAULT_AI_COMMANDS, resolveAiCommandActions, resolveAiCommandPlacement, resolveAiPromptConfig, usesAiCommandMenu, } from "./lib/ai-command-menu";
15
+ export { createSlashCommandExtension, defaultSlashCommands } from "./extensions/slash-command";
16
+ export { createMentionExtension } from "./extensions/mention";
17
+ export { ResizableImage } from "./extensions/resizable-image";
18
+ export { createAiActionExtension } from "./extensions/ai-action";
19
+ export type { SpatikaEditorProps, SpatikaEditorHandle, SpatikaEditorToolbarConfig, SlashCommandItem, MentionContact, AiActionDefinition, AiCommandContext, AiCommandMenuConfig, AiCommandPromptConfig, ImageInsertConfig, PlaceholderConfig, UseSpatikaEditorOptions, } from "./lib/types";
20
+ export { AI_PROMPT_ACTION_ID } from "./lib/types";
21
+ export type { SlashCommandStorage } from "./extensions/slash-command";
22
+ export type { MentionStorage } from "./extensions/mention";
23
+ export type { ImageAlign } from "./extensions/resizable-image";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { SpatikaEditor } from "./composites/SpatikaEditor";
2
+ export { EditorToolbar } from "./composites/EditorToolbar";
3
+ export { EditorBubbleMenu } from "./composites/EditorBubbleMenu";
4
+ export { ImageBubbleMenu } from "./composites/ImageBubbleMenu";
5
+ export { SlashCommandMenu } from "./composites/SlashCommandMenu";
6
+ export { MentionMenu } from "./composites/MentionMenu";
7
+ export { AiCommandMenu } from "./composites/AiCommandMenu";
8
+ export { AiCommandDock } from "./composites/AiCommandDock";
9
+ export { AiCommandPanel } from "./composites/AiCommandPanel";
10
+ export { AiOverlay } from "./composites/AiOverlay";
11
+ export { ToolbarButton } from "./composites/ToolbarButton";
12
+ export { useSpatikaEditor, buildSpatikaExtensions } from "./lib/use-spatika-editor";
13
+ export { DEFAULT_AI_ACTIONS, DEFAULT_AI_COMMANDS, resolveAiCommandActions, resolveAiCommandPlacement, resolveAiPromptConfig, usesAiCommandMenu, } from "./lib/ai-command-menu";
14
+ export { createSlashCommandExtension, defaultSlashCommands } from "./extensions/slash-command";
15
+ export { createMentionExtension } from "./extensions/mention";
16
+ export { ResizableImage } from "./extensions/resizable-image";
17
+ export { createAiActionExtension } from "./extensions/ai-action";
18
+ export { AI_PROMPT_ACTION_ID } from "./lib/types";
@@ -0,0 +1,9 @@
1
+ import type { AiActionDefinition, AiCommandMenuConfig, AiCommandPromptConfig } from "./types";
2
+ export declare const DEFAULT_AI_COMMANDS: AiActionDefinition[];
3
+ /** Legacy three-action set for toolbar/bubble buttons when `aiCommandMenu` is false. */
4
+ export declare const DEFAULT_AI_ACTIONS: AiActionDefinition[];
5
+ export declare function resolveAiCommandActions(menu?: AiCommandMenuConfig | false, actions?: AiActionDefinition[]): AiActionDefinition[];
6
+ export declare function resolveAiPromptConfig(prompt: AiCommandMenuConfig["prompt"] | undefined): Required<AiCommandPromptConfig>;
7
+ export declare function resolveAiCommandPlacement(menu?: AiCommandMenuConfig | false): "toolbar" | "dock";
8
+ export declare function usesAiCommandMenu(aiCommandMenu: AiCommandMenuConfig | false | undefined, hasHandler: boolean): boolean;
9
+ //# sourceMappingURL=ai-command-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-command-menu.d.ts","sourceRoot":"","sources":["../../src/lib/ai-command-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAE9F,eAAO,MAAM,mBAAmB,EAAE,kBAAkB,EAOnD,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,EAAE,kBAAkB,EAIlD,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,IAAI,CAAC,EAAE,mBAAmB,GAAG,KAAK,EAClC,OAAO,CAAC,EAAE,kBAAkB,EAAE,GAC7B,kBAAkB,EAAE,CAMtB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,SAAS,GAChD,QAAQ,CAAC,qBAAqB,CAAC,CAUjC;AAED,wBAAgB,yBAAyB,CACvC,IAAI,CAAC,EAAE,mBAAmB,GAAG,KAAK,GACjC,SAAS,GAAG,MAAM,CAGpB;AAED,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,mBAAmB,GAAG,KAAK,GAAG,SAAS,EACtD,UAAU,EAAE,OAAO,GAClB,OAAO,CAGT"}
@@ -0,0 +1,43 @@
1
+ export const DEFAULT_AI_COMMANDS = [
2
+ { id: "proofread", label: "Proofread selection", requiresSelection: true },
3
+ { id: "rewrite", label: "Rewrite selection", requiresSelection: true },
4
+ { id: "shorten", label: "Make it shorter", requiresSelection: true },
5
+ { id: "improve", label: "Improve writing", requiresSelection: true },
6
+ { id: "expand", label: "Expand", requiresSelection: true },
7
+ { id: "continue", label: "Write a new paragraph", requiresSelection: false },
8
+ ];
9
+ /** Legacy three-action set for toolbar/bubble buttons when `aiCommandMenu` is false. */
10
+ export const DEFAULT_AI_ACTIONS = [
11
+ { id: "improve", label: "Improve writing", requiresSelection: true },
12
+ { id: "shorten", label: "Make shorter", requiresSelection: true },
13
+ { id: "expand", label: "Expand", requiresSelection: true },
14
+ ];
15
+ export function resolveAiCommandActions(menu, actions) {
16
+ if (menu && typeof menu === "object" && menu.actions?.length) {
17
+ return menu.actions;
18
+ }
19
+ if (actions?.length)
20
+ return actions;
21
+ return DEFAULT_AI_COMMANDS;
22
+ }
23
+ export function resolveAiPromptConfig(prompt) {
24
+ if (prompt === false) {
25
+ return { enabled: false, placeholder: "", submitLabel: "Send" };
26
+ }
27
+ const config = prompt === true || prompt === undefined ? {} : prompt;
28
+ return {
29
+ enabled: config.enabled ?? true,
30
+ placeholder: config.placeholder ?? "Ask about this document or request a change…",
31
+ submitLabel: config.submitLabel ?? "Send",
32
+ };
33
+ }
34
+ export function resolveAiCommandPlacement(menu) {
35
+ if (menu === false)
36
+ return "toolbar";
37
+ return menu?.placement ?? "dock";
38
+ }
39
+ export function usesAiCommandMenu(aiCommandMenu, hasHandler) {
40
+ if (!hasHandler)
41
+ return false;
42
+ return aiCommandMenu !== false;
43
+ }
@@ -0,0 +1,4 @@
1
+ import { type ClassValue } from "clsx";
2
+ /** Merge Tailwind class names without conflicts. */
3
+ export declare function cn(...inputs: ClassValue[]): string;
4
+ //# sourceMappingURL=cn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../../src/lib/cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,oDAAoD;AACpD,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
package/dist/lib/cn.js ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ /** Merge Tailwind class names without conflicts. */
4
+ export function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,10 @@
1
+ export type EditorColorSwatch = {
2
+ id: string;
3
+ label: string;
4
+ value: string | null;
5
+ };
6
+ /** Preset text colors for the formatting toolbar. */
7
+ export declare const EDITOR_TEXT_COLORS: EditorColorSwatch[];
8
+ /** Preset highlight colors for the formatting toolbar. */
9
+ export declare const EDITOR_HIGHLIGHT_COLORS: EditorColorSwatch[];
10
+ //# sourceMappingURL=editor-format-colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-format-colors.d.ts","sourceRoot":"","sources":["../../src/lib/editor-format-colors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,EAAE,iBAAiB,EAWjD,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,uBAAuB,EAAE,iBAAiB,EAStD,CAAC"}