@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.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/Block.d.ts +47 -0
- package/dist/Block.d.ts.map +1 -0
- package/dist/Block.js +56 -0
- package/dist/Block.js.map +1 -0
- package/dist/MentionProvider.d.ts +97 -0
- package/dist/MentionProvider.d.ts.map +1 -0
- package/dist/MentionProvider.js +104 -0
- package/dist/MentionProvider.js.map +1 -0
- package/dist/RichTextField.d.ts +286 -0
- package/dist/RichTextField.d.ts.map +1 -0
- package/dist/RichTextField.js +369 -0
- package/dist/RichTextField.js.map +1 -0
- package/dist/extensions/BlockNodeExtension.d.ts +41 -0
- package/dist/extensions/BlockNodeExtension.d.ts.map +1 -0
- package/dist/extensions/BlockNodeExtension.js +103 -0
- package/dist/extensions/BlockNodeExtension.js.map +1 -0
- package/dist/extensions/DragHandleExtension.d.ts +19 -0
- package/dist/extensions/DragHandleExtension.d.ts.map +1 -0
- package/dist/extensions/DragHandleExtension.js +166 -0
- package/dist/extensions/DragHandleExtension.js.map +1 -0
- package/dist/extensions/GridExtension.d.ts +49 -0
- package/dist/extensions/GridExtension.d.ts.map +1 -0
- package/dist/extensions/GridExtension.js +105 -0
- package/dist/extensions/GridExtension.js.map +1 -0
- package/dist/extensions/MentionExtension.d.ts +71 -0
- package/dist/extensions/MentionExtension.d.ts.map +1 -0
- package/dist/extensions/MentionExtension.js +165 -0
- package/dist/extensions/MentionExtension.js.map +1 -0
- package/dist/extensions/MergeTagExtension.d.ts +24 -0
- package/dist/extensions/MergeTagExtension.d.ts.map +1 -0
- package/dist/extensions/MergeTagExtension.js +57 -0
- package/dist/extensions/MergeTagExtension.js.map +1 -0
- package/dist/extensions/SlashCommandExtension.d.ts +71 -0
- package/dist/extensions/SlashCommandExtension.d.ts.map +1 -0
- package/dist/extensions/SlashCommandExtension.js +244 -0
- package/dist/extensions/SlashCommandExtension.js.map +1 -0
- package/dist/extensions/TextSizeMarks.d.ts +33 -0
- package/dist/extensions/TextSizeMarks.d.ts.map +1 -0
- package/dist/extensions/TextSizeMarks.js +47 -0
- package/dist/extensions/TextSizeMarks.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +18 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +25 -0
- package/dist/plugin.js.map +1 -0
- package/dist/react/BlockNodeView.d.ts +19 -0
- package/dist/react/BlockNodeView.d.ts.map +1 -0
- package/dist/react/BlockNodeView.js +60 -0
- package/dist/react/BlockNodeView.js.map +1 -0
- package/dist/react/BlockSidePanel.d.ts +105 -0
- package/dist/react/BlockSidePanel.d.ts.map +1 -0
- package/dist/react/BlockSidePanel.js +339 -0
- package/dist/react/BlockSidePanel.js.map +1 -0
- package/dist/react/FloatingToolbar.d.ts +13 -0
- package/dist/react/FloatingToolbar.d.ts.map +1 -0
- package/dist/react/FloatingToolbar.js +113 -0
- package/dist/react/FloatingToolbar.js.map +1 -0
- package/dist/react/MentionMenu.d.ts +26 -0
- package/dist/react/MentionMenu.d.ts.map +1 -0
- package/dist/react/MentionMenu.js +64 -0
- package/dist/react/MentionMenu.js.map +1 -0
- package/dist/react/Palette.d.ts +26 -0
- package/dist/react/Palette.d.ts.map +1 -0
- package/dist/react/Palette.js +21 -0
- package/dist/react/Palette.js.map +1 -0
- package/dist/react/SlashMenu.d.ts +24 -0
- package/dist/react/SlashMenu.d.ts.map +1 -0
- package/dist/react/SlashMenu.js +74 -0
- package/dist/react/SlashMenu.js.map +1 -0
- package/dist/react/TableFloatingToolbar.d.ts +7 -0
- package/dist/react/TableFloatingToolbar.d.ts.map +1 -0
- package/dist/react/TableFloatingToolbar.js +108 -0
- package/dist/react/TableFloatingToolbar.js.map +1 -0
- package/dist/react/TiptapEditor.d.ts +20 -0
- package/dist/react/TiptapEditor.d.ts.map +1 -0
- package/dist/react/TiptapEditor.js +398 -0
- package/dist/react/TiptapEditor.js.map +1 -0
- package/dist/react/Toolbar.d.ts +45 -0
- package/dist/react/Toolbar.d.ts.map +1 -0
- package/dist/react/Toolbar.js +204 -0
- package/dist/react/Toolbar.js.map +1 -0
- package/dist/react/toolbarButtons.d.ts +36 -0
- package/dist/react/toolbarButtons.d.ts.map +1 -0
- package/dist/react/toolbarButtons.js +300 -0
- package/dist/react/toolbarButtons.js.map +1 -0
- package/dist/register.d.ts +20 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +27 -0
- package/dist/register.js.map +1 -0
- package/dist/render.d.ts +89 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +439 -0
- package/dist/render.js.map +1 -0
- package/package.json +92 -0
- package/src/Block.ts +75 -0
- package/src/MentionProvider.ts +153 -0
- package/src/RichTextField.test.ts +447 -0
- package/src/RichTextField.ts +508 -0
- package/src/extensions/BlockNodeExtension.ts +134 -0
- package/src/extensions/DragHandleExtension.ts +184 -0
- package/src/extensions/GridExtension.test.ts +31 -0
- package/src/extensions/GridExtension.ts +138 -0
- package/src/extensions/MentionExtension.ts +248 -0
- package/src/extensions/MergeTagExtension.ts +75 -0
- package/src/extensions/SlashCommandExtension.test.ts +147 -0
- package/src/extensions/SlashCommandExtension.ts +332 -0
- package/src/extensions/TextSizeMarks.ts +73 -0
- package/src/index.ts +28 -0
- package/src/plugin.test.ts +19 -0
- package/src/plugin.ts +26 -0
- package/src/react/BlockNodeView.tsx +99 -0
- package/src/react/BlockSidePanel.test.ts +412 -0
- package/src/react/BlockSidePanel.tsx +451 -0
- package/src/react/FloatingToolbar.tsx +304 -0
- package/src/react/MentionMenu.tsx +120 -0
- package/src/react/Palette.tsx +86 -0
- package/src/react/SlashMenu.tsx +129 -0
- package/src/react/TableFloatingToolbar.tsx +154 -0
- package/src/react/TiptapEditor.tsx +535 -0
- package/src/react/Toolbar.tsx +438 -0
- package/src/react/toolbarButtons.tsx +579 -0
- package/src/register.test.ts +14 -0
- package/src/register.ts +27 -0
- package/src/render.test.ts +745 -0
- 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"}
|