@pilotiq/tiptap 0.1.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/Block.d.ts +47 -0
  4. package/dist/Block.d.ts.map +1 -0
  5. package/dist/Block.js +56 -0
  6. package/dist/Block.js.map +1 -0
  7. package/dist/MentionProvider.d.ts +97 -0
  8. package/dist/MentionProvider.d.ts.map +1 -0
  9. package/dist/MentionProvider.js +104 -0
  10. package/dist/MentionProvider.js.map +1 -0
  11. package/dist/RichTextField.d.ts +286 -0
  12. package/dist/RichTextField.d.ts.map +1 -0
  13. package/dist/RichTextField.js +369 -0
  14. package/dist/RichTextField.js.map +1 -0
  15. package/dist/extensions/BlockNodeExtension.d.ts +41 -0
  16. package/dist/extensions/BlockNodeExtension.d.ts.map +1 -0
  17. package/dist/extensions/BlockNodeExtension.js +103 -0
  18. package/dist/extensions/BlockNodeExtension.js.map +1 -0
  19. package/dist/extensions/DragHandleExtension.d.ts +19 -0
  20. package/dist/extensions/DragHandleExtension.d.ts.map +1 -0
  21. package/dist/extensions/DragHandleExtension.js +166 -0
  22. package/dist/extensions/DragHandleExtension.js.map +1 -0
  23. package/dist/extensions/GridExtension.d.ts +49 -0
  24. package/dist/extensions/GridExtension.d.ts.map +1 -0
  25. package/dist/extensions/GridExtension.js +105 -0
  26. package/dist/extensions/GridExtension.js.map +1 -0
  27. package/dist/extensions/MentionExtension.d.ts +71 -0
  28. package/dist/extensions/MentionExtension.d.ts.map +1 -0
  29. package/dist/extensions/MentionExtension.js +165 -0
  30. package/dist/extensions/MentionExtension.js.map +1 -0
  31. package/dist/extensions/MergeTagExtension.d.ts +24 -0
  32. package/dist/extensions/MergeTagExtension.d.ts.map +1 -0
  33. package/dist/extensions/MergeTagExtension.js +57 -0
  34. package/dist/extensions/MergeTagExtension.js.map +1 -0
  35. package/dist/extensions/SlashCommandExtension.d.ts +71 -0
  36. package/dist/extensions/SlashCommandExtension.d.ts.map +1 -0
  37. package/dist/extensions/SlashCommandExtension.js +244 -0
  38. package/dist/extensions/SlashCommandExtension.js.map +1 -0
  39. package/dist/extensions/TextSizeMarks.d.ts +33 -0
  40. package/dist/extensions/TextSizeMarks.d.ts.map +1 -0
  41. package/dist/extensions/TextSizeMarks.js +47 -0
  42. package/dist/extensions/TextSizeMarks.js.map +1 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +8 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/plugin.d.ts +18 -0
  48. package/dist/plugin.d.ts.map +1 -0
  49. package/dist/plugin.js +25 -0
  50. package/dist/plugin.js.map +1 -0
  51. package/dist/react/BlockNodeView.d.ts +19 -0
  52. package/dist/react/BlockNodeView.d.ts.map +1 -0
  53. package/dist/react/BlockNodeView.js +60 -0
  54. package/dist/react/BlockNodeView.js.map +1 -0
  55. package/dist/react/BlockSidePanel.d.ts +105 -0
  56. package/dist/react/BlockSidePanel.d.ts.map +1 -0
  57. package/dist/react/BlockSidePanel.js +339 -0
  58. package/dist/react/BlockSidePanel.js.map +1 -0
  59. package/dist/react/FloatingToolbar.d.ts +13 -0
  60. package/dist/react/FloatingToolbar.d.ts.map +1 -0
  61. package/dist/react/FloatingToolbar.js +113 -0
  62. package/dist/react/FloatingToolbar.js.map +1 -0
  63. package/dist/react/MentionMenu.d.ts +26 -0
  64. package/dist/react/MentionMenu.d.ts.map +1 -0
  65. package/dist/react/MentionMenu.js +64 -0
  66. package/dist/react/MentionMenu.js.map +1 -0
  67. package/dist/react/Palette.d.ts +26 -0
  68. package/dist/react/Palette.d.ts.map +1 -0
  69. package/dist/react/Palette.js +21 -0
  70. package/dist/react/Palette.js.map +1 -0
  71. package/dist/react/SlashMenu.d.ts +24 -0
  72. package/dist/react/SlashMenu.d.ts.map +1 -0
  73. package/dist/react/SlashMenu.js +74 -0
  74. package/dist/react/SlashMenu.js.map +1 -0
  75. package/dist/react/TableFloatingToolbar.d.ts +7 -0
  76. package/dist/react/TableFloatingToolbar.d.ts.map +1 -0
  77. package/dist/react/TableFloatingToolbar.js +108 -0
  78. package/dist/react/TableFloatingToolbar.js.map +1 -0
  79. package/dist/react/TiptapEditor.d.ts +20 -0
  80. package/dist/react/TiptapEditor.d.ts.map +1 -0
  81. package/dist/react/TiptapEditor.js +398 -0
  82. package/dist/react/TiptapEditor.js.map +1 -0
  83. package/dist/react/Toolbar.d.ts +45 -0
  84. package/dist/react/Toolbar.d.ts.map +1 -0
  85. package/dist/react/Toolbar.js +204 -0
  86. package/dist/react/Toolbar.js.map +1 -0
  87. package/dist/react/toolbarButtons.d.ts +36 -0
  88. package/dist/react/toolbarButtons.d.ts.map +1 -0
  89. package/dist/react/toolbarButtons.js +300 -0
  90. package/dist/react/toolbarButtons.js.map +1 -0
  91. package/dist/register.d.ts +20 -0
  92. package/dist/register.d.ts.map +1 -0
  93. package/dist/register.js +27 -0
  94. package/dist/register.js.map +1 -0
  95. package/dist/render.d.ts +89 -0
  96. package/dist/render.d.ts.map +1 -0
  97. package/dist/render.js +439 -0
  98. package/dist/render.js.map +1 -0
  99. package/package.json +92 -0
  100. package/src/Block.ts +75 -0
  101. package/src/MentionProvider.ts +153 -0
  102. package/src/RichTextField.test.ts +447 -0
  103. package/src/RichTextField.ts +508 -0
  104. package/src/extensions/BlockNodeExtension.ts +134 -0
  105. package/src/extensions/DragHandleExtension.ts +184 -0
  106. package/src/extensions/GridExtension.test.ts +31 -0
  107. package/src/extensions/GridExtension.ts +138 -0
  108. package/src/extensions/MentionExtension.ts +248 -0
  109. package/src/extensions/MergeTagExtension.ts +75 -0
  110. package/src/extensions/SlashCommandExtension.test.ts +147 -0
  111. package/src/extensions/SlashCommandExtension.ts +332 -0
  112. package/src/extensions/TextSizeMarks.ts +73 -0
  113. package/src/index.ts +28 -0
  114. package/src/plugin.test.ts +19 -0
  115. package/src/plugin.ts +26 -0
  116. package/src/react/BlockNodeView.tsx +99 -0
  117. package/src/react/BlockSidePanel.test.ts +412 -0
  118. package/src/react/BlockSidePanel.tsx +451 -0
  119. package/src/react/FloatingToolbar.tsx +304 -0
  120. package/src/react/MentionMenu.tsx +120 -0
  121. package/src/react/Palette.tsx +86 -0
  122. package/src/react/SlashMenu.tsx +129 -0
  123. package/src/react/TableFloatingToolbar.tsx +154 -0
  124. package/src/react/TiptapEditor.tsx +535 -0
  125. package/src/react/Toolbar.tsx +438 -0
  126. package/src/react/toolbarButtons.tsx +579 -0
  127. package/src/register.test.ts +14 -0
  128. package/src/register.ts +27 -0
  129. package/src/render.test.ts +745 -0
  130. package/src/render.ts +480 -0
@@ -0,0 +1,41 @@
1
+ import { Node } from '@tiptap/core';
2
+ import type { BlockMeta } from '../Block.js';
3
+ declare module '@tiptap/core' {
4
+ interface Commands<ReturnType> {
5
+ customBlock: {
6
+ /** Insert a custom-block node by type, with optional initial data. */
7
+ insertBlock: (blockType: string, blockData?: Record<string, unknown>) => ReturnType;
8
+ };
9
+ }
10
+ }
11
+ export interface BlockNodeOptions {
12
+ /**
13
+ * Block-meta registry. The NodeView reads from this to find the schema
14
+ * for the block-type it's rendering. Stashed on the extension's options
15
+ * because Tiptap's ReactNodeViewRenderer mounts NodeViews in a separate
16
+ * React tree — `useContext` does NOT reach them, so we can't pass
17
+ * registry data via React context.
18
+ */
19
+ blocks: BlockMeta[];
20
+ /**
21
+ * Bridge from the NodeView's separate React tree back to the editor's
22
+ * own tree, where the side panel lives. Set by `TiptapEditor` so the
23
+ * "Edit" button on each block can request the panel open against this
24
+ * specific node. `undefined` means no host is listening — the NodeView
25
+ * falls back to a no-op (does not render an Edit affordance, or does
26
+ * so disabled, depending on the consumer's chrome).
27
+ */
28
+ onEdit?: (pos: number) => void;
29
+ }
30
+ /**
31
+ * Single ProseMirror node type that represents every custom block. The
32
+ * concrete block type ("callout", "image", …) lives in `attrs.blockType`,
33
+ * and the per-block data lives in `attrs.blockData`. The React NodeView
34
+ * looks the type up in this extension's `options.blocks` and renders the
35
+ * matching inline form.
36
+ *
37
+ * Storing one node type per block name would scale O(n) extensions. This
38
+ * approach scales O(1).
39
+ */
40
+ export declare const BlockNodeExtension: Node<BlockNodeOptions, any>;
41
+ //# sourceMappingURL=BlockNodeExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockNodeExtension.d.ts","sourceRoot":"","sources":["../../src/extensions/BlockNodeExtension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAA;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAG5C,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,WAAW,EAAE;YACX,sEAAsE;YACtE,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,UAAU,CAAA;SACpF,CAAA;KACF;CACF;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,6BAyF7B,CAAA"}
@@ -0,0 +1,103 @@
1
+ import { Node, mergeAttributes } from '@tiptap/core';
2
+ import { ReactNodeViewRenderer } from '@tiptap/react';
3
+ import { BlockNodeView } from '../react/BlockNodeView.js';
4
+ /**
5
+ * Single ProseMirror node type that represents every custom block. The
6
+ * concrete block type ("callout", "image", …) lives in `attrs.blockType`,
7
+ * and the per-block data lives in `attrs.blockData`. The React NodeView
8
+ * looks the type up in this extension's `options.blocks` and renders the
9
+ * matching inline form.
10
+ *
11
+ * Storing one node type per block name would scale O(n) extensions. This
12
+ * approach scales O(1).
13
+ */
14
+ export const BlockNodeExtension = Node.create({
15
+ // Avoid `name: 'block'` — ProseMirror's `block` is a schema group name,
16
+ // and naming a node identically to a group can collide subtly with
17
+ // schema content matching (TrailingNode threw "invalid content" on every
18
+ // dispatch with `name: 'block'`).
19
+ name: 'pilotiqBlock',
20
+ group: 'block',
21
+ // Mirrors the canonical Tiptap atom-block pattern (image / horizontalRule):
22
+ // omit `atom`/`selectable`, set `draggable: true`, no explicit `content`.
23
+ // Setting `atom: true` together with `group: 'block'` was making
24
+ // StarterKit's TrailingNode plugin throw "invalid content" on every
25
+ // dispatch — even before any block was inserted.
26
+ draggable: true,
27
+ addOptions() {
28
+ // `onEdit` intentionally omitted — `exactOptionalPropertyTypes` makes
29
+ // an explicit `undefined` non-assignable to the optional field, and
30
+ // the host wires it via `BlockNodeExtension.configure({ onEdit })`.
31
+ return { blocks: [] };
32
+ },
33
+ addAttributes() {
34
+ return {
35
+ blockType: {
36
+ default: null,
37
+ parseHTML: (el) => el.getAttribute('data-block-type'),
38
+ renderHTML: (attrs) => {
39
+ if (!attrs['blockType'])
40
+ return {};
41
+ return { 'data-block-type': attrs['blockType'] };
42
+ },
43
+ },
44
+ blockData: {
45
+ // Default `null` rather than `{}` — ProseMirror compares attrs by
46
+ // reference for some equality checks, and a fresh `{}` per node
47
+ // create call breaks them subtly.
48
+ default: null,
49
+ parseHTML: (el) => {
50
+ const raw = el.getAttribute('data-block-data');
51
+ if (!raw)
52
+ return null;
53
+ try {
54
+ return JSON.parse(raw);
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ },
60
+ renderHTML: (attrs) => {
61
+ if (!attrs['blockData'])
62
+ return {};
63
+ return { 'data-block-data': JSON.stringify(attrs['blockData']) };
64
+ },
65
+ },
66
+ };
67
+ },
68
+ parseHTML() {
69
+ return [{ tag: 'div[data-pilotiq-block]' }];
70
+ },
71
+ renderHTML({ HTMLAttributes }) {
72
+ return ['div', mergeAttributes({ 'data-pilotiq-block': '' }, HTMLAttributes)];
73
+ },
74
+ addNodeView() {
75
+ return ReactNodeViewRenderer(BlockNodeView);
76
+ },
77
+ addCommands() {
78
+ return {
79
+ insertBlock: (blockType, blockData = {}) => ({ commands }) => commands.insertContent({
80
+ type: this.name,
81
+ attrs: { blockType, blockData },
82
+ }),
83
+ };
84
+ },
85
+ // `Mod-e` opens the side panel for the currently NodeSelected block.
86
+ // Returns false when no block is selected so the browser's default
87
+ // (Safari "Use Selection for Find", etc.) still applies in plain text.
88
+ addKeyboardShortcuts() {
89
+ return {
90
+ 'Mod-e': () => {
91
+ const onEdit = this.options.onEdit;
92
+ if (!onEdit)
93
+ return false;
94
+ const sel = this.editor.state.selection;
95
+ if (sel.node?.type.name !== this.name)
96
+ return false;
97
+ onEdit(sel.from);
98
+ return true;
99
+ },
100
+ };
101
+ },
102
+ });
103
+ //# sourceMappingURL=BlockNodeExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockNodeExtension.js","sourceRoot":"","sources":["../../src/extensions/BlockNodeExtension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Extension } from '@tiptap/core';
2
+ /**
3
+ * Hand-built drag handle: floats a six-dot button in the gutter on the left
4
+ * of whichever top-level block the cursor is hovering. Click-drag to reorder.
5
+ *
6
+ * Implementation (kept self-contained, no `tiptap-extension-global-drag-handle`
7
+ * dep — that pkg uses Tiptap internals that break across versions):
8
+ *
9
+ * 1. On mount, append a single `<button>` to `document.body`. It's the
10
+ * handle. Position lives in CSS `top` / `left`.
11
+ * 2. On `mousemove` over the editor, find the top-level block under the
12
+ * cursor via `posAtCoords` + climb to depth 1, then read its DOM rect
13
+ * and align the handle to its top-left.
14
+ * 3. On `mousedown` on the handle, set `node` selection on that block and
15
+ * dispatch a synthetic `dragstart` from the editor DOM — ProseMirror's
16
+ * own drop logic handles reorder.
17
+ */
18
+ export declare const DragHandleExtension: Extension<any, any>;
19
+ //# sourceMappingURL=DragHandleExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragHandleExtension.d.ts","sourceRoot":"","sources":["../../src/extensions/DragHandleExtension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAYxC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,qBAW9B,CAAA"}
@@ -0,0 +1,166 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { NodeSelection, Plugin, PluginKey } from '@tiptap/pm/state';
3
+ const dragHandlePluginKey = new PluginKey('pilotiqDragHandle');
4
+ // Node types whose direct children are the draggable units (e.g. each
5
+ // `listItem` of a `bulletList` is a draggable unit). Listed forward-compat
6
+ // includes `taskList` even though StarterKit 3 doesn't ship it; if a consumer
7
+ // registers it later it'll just work.
8
+ const LIST_CONTAINERS = new Set(['bulletList', 'orderedList', 'taskList']);
9
+ /**
10
+ * Hand-built drag handle: floats a six-dot button in the gutter on the left
11
+ * of whichever top-level block the cursor is hovering. Click-drag to reorder.
12
+ *
13
+ * Implementation (kept self-contained, no `tiptap-extension-global-drag-handle`
14
+ * dep — that pkg uses Tiptap internals that break across versions):
15
+ *
16
+ * 1. On mount, append a single `<button>` to `document.body`. It's the
17
+ * handle. Position lives in CSS `top` / `left`.
18
+ * 2. On `mousemove` over the editor, find the top-level block under the
19
+ * cursor via `posAtCoords` + climb to depth 1, then read its DOM rect
20
+ * and align the handle to its top-left.
21
+ * 3. On `mousedown` on the handle, set `node` selection on that block and
22
+ * dispatch a synthetic `dragstart` from the editor DOM — ProseMirror's
23
+ * own drop logic handles reorder.
24
+ */
25
+ export const DragHandleExtension = Extension.create({
26
+ name: 'pilotiqDragHandle',
27
+ addProseMirrorPlugins() {
28
+ return [
29
+ new Plugin({
30
+ key: dragHandlePluginKey,
31
+ view: (view) => createDragHandleView(view),
32
+ }),
33
+ ];
34
+ },
35
+ });
36
+ function createDragHandleView(view) {
37
+ const handle = document.createElement('button');
38
+ handle.type = 'button';
39
+ handle.setAttribute('data-pilotiq-drag-handle', '');
40
+ handle.setAttribute('contenteditable', 'false');
41
+ handle.setAttribute('aria-label', 'Drag block');
42
+ handle.setAttribute('draggable', 'true');
43
+ handle.style.cssText = [
44
+ 'position: absolute',
45
+ 'display: none',
46
+ 'cursor: grab',
47
+ 'background: transparent',
48
+ 'border: 0',
49
+ 'padding: 2px',
50
+ 'color: var(--muted-foreground, #888)',
51
+ 'opacity: 0',
52
+ 'transition: opacity 0.15s',
53
+ 'z-index: 50',
54
+ 'line-height: 1',
55
+ 'border-radius: 4px',
56
+ ].join(';');
57
+ handle.innerHTML = '<svg width="8" height="12" viewBox="0 0 8 12" fill="currentColor" aria-hidden="true"><circle cx="2" cy="2" r="1"/><circle cx="2" cy="6" r="1"/><circle cx="2" cy="10" r="1"/><circle cx="6" cy="2" r="1"/><circle cx="6" cy="6" r="1"/><circle cx="6" cy="10" r="1"/></svg>';
58
+ document.body.appendChild(handle);
59
+ let activePos = null;
60
+ const onMouseMove = (event) => {
61
+ const editorRect = view.dom.getBoundingClientRect();
62
+ const inside = event.clientX >= editorRect.left - 60 &&
63
+ event.clientX <= editorRect.right &&
64
+ event.clientY >= editorRect.top &&
65
+ event.clientY <= editorRect.bottom;
66
+ if (!inside) {
67
+ hide();
68
+ return;
69
+ }
70
+ const pos = view.posAtCoords({ left: event.clientX, top: event.clientY });
71
+ if (!pos) {
72
+ hide();
73
+ return;
74
+ }
75
+ const $pos = view.state.doc.resolve(pos.pos);
76
+ // Pick the deepest ancestor whose parent is the doc OR a list container —
77
+ // that's the "unit" the user expects the handle to grab. So inside a
78
+ // bullet list, hovering an item resolves to the list_item (not the whole
79
+ // bullet_list); inside a blockquote, hovering its inner paragraph resolves
80
+ // to the blockquote (since the blockquote's parent is the doc).
81
+ let blockDepth = 1;
82
+ for (let d = $pos.depth; d >= 1; d--) {
83
+ const parentName = $pos.node(d - 1).type.name;
84
+ if (parentName === 'doc' || LIST_CONTAINERS.has(parentName)) {
85
+ blockDepth = d;
86
+ break;
87
+ }
88
+ }
89
+ const blockPos = $pos.before(blockDepth);
90
+ const blockNode = view.nodeDOM(blockPos);
91
+ if (!blockNode) {
92
+ hide();
93
+ return;
94
+ }
95
+ const rect = blockNode.getBoundingClientRect();
96
+ handle.style.display = 'block';
97
+ handle.style.opacity = '1';
98
+ handle.style.top = `${rect.top + window.scrollY + 4}px`;
99
+ // Pin X to the editor's left gutter (not the block's left edge). For
100
+ // nested content the block's `rect.left` sits past the indent / bullet
101
+ // gutter, so handles for list items would overlap their bullets. Keeping
102
+ // the handle in a fixed column lets the eye track which block it points
103
+ // at by vertical alignment alone.
104
+ handle.style.left = `${editorRect.left + window.scrollX + 16}px`;
105
+ activePos = blockPos;
106
+ };
107
+ const hide = () => {
108
+ handle.style.opacity = '0';
109
+ handle.style.display = 'none';
110
+ activePos = null;
111
+ };
112
+ const onMouseLeave = (event) => {
113
+ // Don't hide while moving onto the handle itself.
114
+ if (event.relatedTarget === handle)
115
+ return;
116
+ hide();
117
+ };
118
+ const onDragStart = (event) => {
119
+ if (activePos === null || !event.dataTransfer)
120
+ return;
121
+ const node = view.state.doc.nodeAt(activePos);
122
+ if (!node)
123
+ return;
124
+ // Select the block as a NodeSelection so PM treats the drag as a node
125
+ // move, not a text-range move.
126
+ const selection = NodeSelection.create(view.state.doc, activePos);
127
+ view.dispatch(view.state.tr.setSelection(selection));
128
+ // PM's drop handler reads `view.dragging` for in-editor drags. Without
129
+ // it the drop falls back to clipboard-HTML parsing and silently no-ops
130
+ // (drop returns to origin). This is the line that actually fixes drop.
131
+ const slice = selection.content();
132
+ view.dragging = {
133
+ slice,
134
+ move: !event.ctrlKey && !event.metaKey,
135
+ };
136
+ // Use PM's clipboard serializer so the drag carries proper HTML — both
137
+ // for cross-editor pastes and as the visible drag image.
138
+ const { dom, text } = view.serializeForClipboard(slice);
139
+ event.dataTransfer.clearData();
140
+ event.dataTransfer.setData('text/html', dom.innerHTML);
141
+ event.dataTransfer.setData('text/plain', text);
142
+ event.dataTransfer.effectAllowed = 'copyMove';
143
+ // Drag image = the actual block, not the handle button.
144
+ const blockNode = view.nodeDOM(activePos);
145
+ if (blockNode)
146
+ event.dataTransfer.setDragImage(blockNode, 0, 0);
147
+ handle.style.cursor = 'grabbing';
148
+ };
149
+ const onDragEnd = () => {
150
+ handle.style.cursor = 'grab';
151
+ };
152
+ view.dom.addEventListener('mousemove', onMouseMove);
153
+ view.dom.addEventListener('mouseleave', onMouseLeave);
154
+ handle.addEventListener('dragstart', onDragStart);
155
+ handle.addEventListener('dragend', onDragEnd);
156
+ return {
157
+ destroy: () => {
158
+ view.dom.removeEventListener('mousemove', onMouseMove);
159
+ view.dom.removeEventListener('mouseleave', onMouseLeave);
160
+ handle.removeEventListener('dragstart', onDragStart);
161
+ handle.removeEventListener('dragend', onDragEnd);
162
+ handle.remove();
163
+ },
164
+ };
165
+ }
166
+ //# sourceMappingURL=DragHandleExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragHandleExtension.js","sourceRoot":"","sources":["../../src/extensions/DragHandleExtension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { Node } from '@tiptap/core';
2
+ declare module '@tiptap/core' {
3
+ interface Commands<ReturnType> {
4
+ grid: {
5
+ /**
6
+ * Insert a multi-column grid at the cursor. Each column gets one
7
+ * empty paragraph. Defaults to 2 columns; pass `{ columns: 3 }` for
8
+ * the three-column variant.
9
+ */
10
+ setGrid: (options?: {
11
+ columns?: GridColumns;
12
+ }) => ReturnType;
13
+ /**
14
+ * Unwrap the enclosing grid: replace the grid node with the flat
15
+ * concatenation of every column's children. No-op when the cursor
16
+ * isn't inside a grid.
17
+ */
18
+ unsetGrid: () => ReturnType;
19
+ };
20
+ }
21
+ }
22
+ /** Allowed column counts. */
23
+ export type GridColumns = 2 | 3;
24
+ /**
25
+ * Coerce any input into one of the allowed column counts. Out-of-range
26
+ * values, NaN, undefined, and non-numeric strings all fall back to 2 — the
27
+ * conservative default.
28
+ *
29
+ * Exported for unit tests and so the renderer in `render.ts` can share the
30
+ * same validator with the editor's `parseHTML`.
31
+ */
32
+ export declare function clampGridColumns(raw: unknown): GridColumns;
33
+ /**
34
+ * Multi-column grid container. Schema constrains it to exactly 2 or 3
35
+ * `gridColumn` children, so the user can't construct a 1-column or
36
+ * 4-column grid through any path (toolbar, slash, paste).
37
+ *
38
+ * Renders to `<div data-type="grid" data-columns="N" class="pilotiq-grid
39
+ * pilotiq-grid-cols-N">` — consumers ship the matching CSS (Tailwind
40
+ * `grid grid-cols-N gap-4` or hand-rolled). Same posture as `lead` /
41
+ * `small` size marks: the package stays CSS-free.
42
+ */
43
+ export declare const Grid: Node<any, any>;
44
+ /**
45
+ * Individual column inside a `grid`. Belongs to its own custom group so the
46
+ * top-level document only accepts `Grid` (not bare `gridColumn`s).
47
+ */
48
+ export declare const GridColumn: Node<any, any>;
49
+ //# sourceMappingURL=GridExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GridExtension.d.ts","sourceRoot":"","sources":["../../src/extensions/GridExtension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAA;AAEpD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,IAAI,EAAE;YACJ;;;;eAIG;YACH,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE;gBAAE,OAAO,CAAC,EAAE,WAAW,CAAA;aAAE,KAAK,UAAU,CAAA;YAC5D;;;;eAIG;YACH,SAAS,EAAE,MAAM,UAAU,CAAA;SAC5B,CAAA;KACF;CACF;AAED,6BAA6B;AAC7B,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,CAAA;AAI/B;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAK1D;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI,gBAmEf,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,gBAarB,CAAA"}
@@ -0,0 +1,105 @@
1
+ import { Node, mergeAttributes } from '@tiptap/core';
2
+ const ALLOWED_COLUMNS = [2, 3];
3
+ /**
4
+ * Coerce any input into one of the allowed column counts. Out-of-range
5
+ * values, NaN, undefined, and non-numeric strings all fall back to 2 — the
6
+ * conservative default.
7
+ *
8
+ * Exported for unit tests and so the renderer in `render.ts` can share the
9
+ * same validator with the editor's `parseHTML`.
10
+ */
11
+ export function clampGridColumns(raw) {
12
+ const n = typeof raw === 'number' ? raw : Number(raw);
13
+ if (!Number.isFinite(n))
14
+ return 2;
15
+ const trunc = Math.trunc(n);
16
+ return ALLOWED_COLUMNS.includes(trunc) ? trunc : 2;
17
+ }
18
+ /**
19
+ * Multi-column grid container. Schema constrains it to exactly 2 or 3
20
+ * `gridColumn` children, so the user can't construct a 1-column or
21
+ * 4-column grid through any path (toolbar, slash, paste).
22
+ *
23
+ * Renders to `<div data-type="grid" data-columns="N" class="pilotiq-grid
24
+ * pilotiq-grid-cols-N">` — consumers ship the matching CSS (Tailwind
25
+ * `grid grid-cols-N gap-4` or hand-rolled). Same posture as `lead` /
26
+ * `small` size marks: the package stays CSS-free.
27
+ */
28
+ export const Grid = Node.create({
29
+ name: 'grid',
30
+ group: 'block',
31
+ content: 'gridColumn{2,3}',
32
+ defining: true,
33
+ addAttributes() {
34
+ return {
35
+ columns: {
36
+ default: 2,
37
+ parseHTML: (el) => clampGridColumns(el.getAttribute('data-columns')),
38
+ renderHTML: (attrs) => {
39
+ const cols = clampGridColumns(attrs['columns']);
40
+ return {
41
+ 'data-columns': String(cols),
42
+ class: `pilotiq-grid pilotiq-grid-cols-${cols}`,
43
+ };
44
+ },
45
+ },
46
+ };
47
+ },
48
+ parseHTML() {
49
+ return [{ tag: 'div[data-type="grid"]' }];
50
+ },
51
+ renderHTML({ HTMLAttributes }) {
52
+ return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'grid' }), 0];
53
+ },
54
+ addCommands() {
55
+ return {
56
+ setGrid: (options) => ({ chain }) => {
57
+ const columns = clampGridColumns(options?.columns ?? 2);
58
+ const content = Array.from({ length: columns }, () => ({
59
+ type: 'gridColumn',
60
+ content: [{ type: 'paragraph' }],
61
+ }));
62
+ return chain()
63
+ .focus()
64
+ .insertContent({ type: 'grid', attrs: { columns }, content })
65
+ .run();
66
+ },
67
+ unsetGrid: () => ({ state, tr, dispatch }) => {
68
+ const $head = state.selection.$head;
69
+ for (let depth = $head.depth; depth > 0; depth--) {
70
+ const node = $head.node(depth);
71
+ if (node.type.name !== 'grid')
72
+ continue;
73
+ const start = $head.before(depth);
74
+ const end = $head.after(depth);
75
+ // Concatenate each column's children into a flat block list.
76
+ const flat = [];
77
+ node.forEach((col) => {
78
+ col.forEach((child) => flat.push(child));
79
+ });
80
+ if (dispatch)
81
+ tr.replaceWith(start, end, flat);
82
+ return true;
83
+ }
84
+ return false;
85
+ },
86
+ };
87
+ },
88
+ });
89
+ /**
90
+ * Individual column inside a `grid`. Belongs to its own custom group so the
91
+ * top-level document only accepts `Grid` (not bare `gridColumn`s).
92
+ */
93
+ export const GridColumn = Node.create({
94
+ name: 'gridColumn',
95
+ group: 'gridColumn',
96
+ content: 'block+',
97
+ defining: true,
98
+ parseHTML() {
99
+ return [{ tag: 'div[data-type="gridColumn"]' }];
100
+ },
101
+ renderHTML({ HTMLAttributes }) {
102
+ return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'gridColumn' }), 0];
103
+ },
104
+ });
105
+ //# sourceMappingURL=GridExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GridExtension.js","sourceRoot":"","sources":["../../src/extensions/GridExtension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { Node } from '@tiptap/core';
2
+ import type { MentionItem, MentionProviderMeta } from '../MentionProvider.js';
3
+ declare module '@tiptap/core' {
4
+ interface Commands<ReturnType> {
5
+ mention: {
6
+ /**
7
+ * Insert a `mention` atom node. `trigger` is the character the
8
+ * provider was registered with (`@` / `#` / …) and is rendered
9
+ * inline together with the resolved label.
10
+ */
11
+ insertMention: (args: {
12
+ id: string;
13
+ label: string;
14
+ trigger: string;
15
+ }) => ReturnType;
16
+ };
17
+ }
18
+ }
19
+ /**
20
+ * State the React side of the editor needs to render the mention popover.
21
+ * `null` when the popover should be unmounted.
22
+ */
23
+ export interface MentionState {
24
+ trigger: string;
25
+ items: MentionItem[];
26
+ /** Pick item — Suggestion will replace the trigger range and run the command. */
27
+ command: (item: MentionItem) => void;
28
+ clientRect: () => DOMRect | null;
29
+ query: string;
30
+ }
31
+ export interface MentionOptions {
32
+ providers: MentionProviderMeta[];
33
+ /**
34
+ * Called whenever the popover should mount, update, or unmount. TiptapEditor
35
+ * holds the React state and passes a setter here.
36
+ */
37
+ onStateChange: (state: MentionState | null) => void;
38
+ /**
39
+ * URL the field's `tagRichTextMentionUrls` walker stamped on the wire-side
40
+ * meta. Required for async providers (`MentionProvider.itemsUsing(fn)`);
41
+ * unused when every provider on this field is static. The client POSTs
42
+ * `{ field, trigger, query }` per keystroke and expects `{ ok, items }`.
43
+ */
44
+ mentionsUrl?: string;
45
+ /**
46
+ * Field path the route handler uses to find the RichTextField on the
47
+ * page. Equals `Field.name` for non-nested fields. Inside a Repeater
48
+ * row this is the dotted form `<repeaterName>.<index>.<innerName>`;
49
+ * inside a Builder row it's `<builderName>.<index>.data.<innerName>`.
50
+ * The route handler parses the prefix and looks up the field against
51
+ * the Repeater's template / each Builder block's schema.
52
+ */
53
+ fieldName?: string;
54
+ }
55
+ /**
56
+ * Inline atom node + a Suggestion plugin per registered provider.
57
+ *
58
+ * The node carries `id`, `label`, and `trigger` attributes. Storage is JSON:
59
+ * `{ type: 'mention', attrs: { id: 'sleman', label: 'Sleman', trigger: '@' } }`.
60
+ *
61
+ * Each provider gets its own ProseMirror Suggestion plugin instance so users
62
+ * can mix multiple trigger characters in the same editor (`@user`, `#room`).
63
+ * Items are static — declared via `MentionProvider.make('@').items([...])`.
64
+ *
65
+ * Read-side rendering happens through `renderRichTextToHtml(content,
66
+ * { resolveMention: (trigger, id) => latestLabel })`. Without an override
67
+ * the cached label is used — the editor stamps it at insert time so
68
+ * static-content snapshots stay self-contained.
69
+ */
70
+ export declare const MentionExtension: Node<MentionOptions, any>;
71
+ //# sourceMappingURL=MentionExtension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MentionExtension.d.ts","sourceRoot":"","sources":["../../src/extensions/MentionExtension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAA4C,MAAM,cAAc,CAAA;AAG7E,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAE7E,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,OAAO,EAAE;YACP;;;;eAIG;YACH,aAAa,EAAE,CAAC,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAA;SACpF,CAAA;KACF;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAK,MAAM,CAAA;IAClB,KAAK,EAAO,WAAW,EAAE,CAAA;IACzB,iFAAiF;IACjF,OAAO,EAAK,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,UAAU,EAAE,MAAM,OAAO,GAAG,IAAI,CAAA;IAChC,KAAK,EAAO,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,mBAAmB,EAAE,CAAA;IAChC;;;OAGG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAA;IACnD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,2BA6G3B,CAAA"}