@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,47 @@
|
|
|
1
|
+
import { Mark, mergeAttributes } from '@tiptap/core';
|
|
2
|
+
/**
|
|
3
|
+
* Two inline marks for paragraph-style size variants beyond the standard
|
|
4
|
+
* heading levels:
|
|
5
|
+
*
|
|
6
|
+
* - `lead` — opening / lede paragraph styling. Renders as
|
|
7
|
+
* `<span class="lead">…</span>` so authors keep paragraph
|
|
8
|
+
* semantics; styling is owned by the consumer's CSS (the
|
|
9
|
+
* adapter doesn't ship a `.lead` rule — every site already
|
|
10
|
+
* has one).
|
|
11
|
+
* - `small` — semantic `<small>` mark. Mirrors the HTML element so
|
|
12
|
+
* read-side renderers don't need a special class to style
|
|
13
|
+
* fine print.
|
|
14
|
+
*
|
|
15
|
+
* Both marks live in the standard inline group so they compose with bold,
|
|
16
|
+
* italic, color, etc. without exclusivity rules. Excluding each other isn't
|
|
17
|
+
* useful — a `<small lead>` selection would be inert visually anyway.
|
|
18
|
+
*/
|
|
19
|
+
export const LeadMarkExtension = Mark.create({
|
|
20
|
+
name: 'lead',
|
|
21
|
+
parseHTML() {
|
|
22
|
+
return [{ tag: 'span.lead' }];
|
|
23
|
+
},
|
|
24
|
+
renderHTML({ HTMLAttributes }) {
|
|
25
|
+
return ['span', mergeAttributes({ class: 'lead' }, HTMLAttributes), 0];
|
|
26
|
+
},
|
|
27
|
+
addCommands() {
|
|
28
|
+
return {
|
|
29
|
+
toggleLead: () => ({ commands }) => commands.toggleMark(this.name),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
export const SmallMarkExtension = Mark.create({
|
|
34
|
+
name: 'small',
|
|
35
|
+
parseHTML() {
|
|
36
|
+
return [{ tag: 'small' }];
|
|
37
|
+
},
|
|
38
|
+
renderHTML({ HTMLAttributes }) {
|
|
39
|
+
return ['small', mergeAttributes(HTMLAttributes), 0];
|
|
40
|
+
},
|
|
41
|
+
addCommands() {
|
|
42
|
+
return {
|
|
43
|
+
toggleSmall: () => ({ commands }) => commands.toggleMark(this.name),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=TextSizeMarks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextSizeMarks.js","sourceRoot":"","sources":["../../src/extensions/TextSizeMarks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAepD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,MAAM;IAEZ,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAA;IAC/B,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,CAAA;IACxE,CAAC;IAED,WAAW;QACT,OAAO;YACL,UAAU,EACR,GAAG,EAAE,CACL,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CACf,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;SACnC,CAAA;IACH,CAAC;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,OAAO;IAEb,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;IAC3B,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,OAAO,EAAE,eAAe,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAA;IACtD,CAAC;IAED,WAAW;QACT,OAAO;YACL,WAAW,EACT,GAAG,EAAE,CACL,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CACf,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;SACnC,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { RichTextField, DEFAULT_TOOLBAR_GROUPS, DEFAULT_TEXT_COLORS, DEFAULT_HIGHLIGHT_COLORS, type ColorSwatch, type RichTextAttachmentVisibility, type RichTextFieldMeta, type RichTextStorage, type ToolbarButtonId, type ToolbarGroups, } from './RichTextField.js';
|
|
2
|
+
export { Block, type BlockMeta } from './Block.js';
|
|
3
|
+
export { MentionProvider, type MentionItem, type MentionProviderMeta, } from './MentionProvider.js';
|
|
4
|
+
export { registerTiptap } from './register.js';
|
|
5
|
+
export { tiptap } from './plugin.js';
|
|
6
|
+
export { TiptapEditor } from './react/TiptapEditor.js';
|
|
7
|
+
export { renderRichTextToHtml, isRichTextValue, type RenderRichTextOptions, type TiptapNode, type TiptapMark, } from './render.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,WAAW,EAChB,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EACL,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { RichTextField, DEFAULT_TOOLBAR_GROUPS, DEFAULT_TEXT_COLORS, DEFAULT_HIGHLIGHT_COLORS, } from './RichTextField.js';
|
|
2
|
+
export { Block } from './Block.js';
|
|
3
|
+
export { MentionProvider, } from './MentionProvider.js';
|
|
4
|
+
export { registerTiptap } from './register.js';
|
|
5
|
+
export { tiptap } from './plugin.js';
|
|
6
|
+
export { TiptapEditor } from './react/TiptapEditor.js';
|
|
7
|
+
export { renderRichTextToHtml, isRichTextValue, } from './render.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,GAOzB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,EAAkB,MAAM,YAAY,CAAA;AAClD,OAAO,EACL,eAAe,GAGhB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EACL,oBAAoB,EACpB,eAAe,GAIhB,MAAM,aAAa,CAAA"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PilotiqPlugin } from '@pilotiq/pilotiq';
|
|
2
|
+
/**
|
|
3
|
+
* Pilotiq plugin that registers the Tiptap rich-text renderer.
|
|
4
|
+
*
|
|
5
|
+
* Use with `Pilotiq.make(...).plugins([tiptap()])` instead of calling
|
|
6
|
+
* `registerTiptap()` directly from your app's client entry — the plugin
|
|
7
|
+
* runs through the panel module so it's wired in one place.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Pilotiq } from '@pilotiq/pilotiq'
|
|
12
|
+
* import { tiptap } from '@pilotiq/tiptap'
|
|
13
|
+
*
|
|
14
|
+
* Pilotiq.make('Admin').plugins([tiptap()])
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function tiptap(): PilotiqPlugin;
|
|
18
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGrD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,IAAI,aAAa,CAOtC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { registerTiptap } from './register.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pilotiq plugin that registers the Tiptap rich-text renderer.
|
|
4
|
+
*
|
|
5
|
+
* Use with `Pilotiq.make(...).plugins([tiptap()])` instead of calling
|
|
6
|
+
* `registerTiptap()` directly from your app's client entry — the plugin
|
|
7
|
+
* runs through the panel module so it's wired in one place.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Pilotiq } from '@pilotiq/pilotiq'
|
|
12
|
+
* import { tiptap } from '@pilotiq/tiptap'
|
|
13
|
+
*
|
|
14
|
+
* Pilotiq.make('Admin').plugins([tiptap()])
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function tiptap() {
|
|
18
|
+
return {
|
|
19
|
+
name: '@pilotiq/tiptap',
|
|
20
|
+
register() {
|
|
21
|
+
registerTiptap();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM;IACpB,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,QAAQ;YACN,cAAc,EAAE,CAAA;QAClB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type NodeViewProps } from '@tiptap/react';
|
|
2
|
+
/**
|
|
3
|
+
* Generic React NodeView for the `pilotiqBlock` ProseMirror node. Reads
|
|
4
|
+
* the block type from `node.attrs.blockType`, looks up its `BlockMeta`
|
|
5
|
+
* in `BlockNodeExtension.options.blocks`, and renders a compact inline
|
|
6
|
+
* summary card with an "Edit" button.
|
|
7
|
+
*
|
|
8
|
+
* Editing happens in a side panel hosted by `TiptapEditor`, NOT inline.
|
|
9
|
+
* The NodeView fires `BlockNodeExtension.options.onEdit(getPos())` when
|
|
10
|
+
* the Edit button is clicked; the host opens its panel anchored to the
|
|
11
|
+
* editor wrapper. NodeViews live in a separate React tree from the host
|
|
12
|
+
* editor, so the bridge has to go through extension options — context
|
|
13
|
+
* doesn't cross trees.
|
|
14
|
+
*
|
|
15
|
+
* If no `onEdit` is wired (e.g. a consumer that uses `BlockNodeExtension`
|
|
16
|
+
* standalone without `TiptapEditor`'s panel), the Edit button is hidden.
|
|
17
|
+
*/
|
|
18
|
+
export declare function BlockNodeView(props: NodeViewProps): import("react/jsx-runtime").JSX.Element | null;
|
|
19
|
+
//# sourceMappingURL=BlockNodeView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockNodeView.d.ts","sourceRoot":"","sources":["../../src/react/BlockNodeView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAGnE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,kDA8EjD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { NodeViewWrapper } from '@tiptap/react';
|
|
4
|
+
/**
|
|
5
|
+
* Generic React NodeView for the `pilotiqBlock` ProseMirror node. Reads
|
|
6
|
+
* the block type from `node.attrs.blockType`, looks up its `BlockMeta`
|
|
7
|
+
* in `BlockNodeExtension.options.blocks`, and renders a compact inline
|
|
8
|
+
* summary card with an "Edit" button.
|
|
9
|
+
*
|
|
10
|
+
* Editing happens in a side panel hosted by `TiptapEditor`, NOT inline.
|
|
11
|
+
* The NodeView fires `BlockNodeExtension.options.onEdit(getPos())` when
|
|
12
|
+
* the Edit button is clicked; the host opens its panel anchored to the
|
|
13
|
+
* editor wrapper. NodeViews live in a separate React tree from the host
|
|
14
|
+
* editor, so the bridge has to go through extension options — context
|
|
15
|
+
* doesn't cross trees.
|
|
16
|
+
*
|
|
17
|
+
* If no `onEdit` is wired (e.g. a consumer that uses `BlockNodeExtension`
|
|
18
|
+
* standalone without `TiptapEditor`'s panel), the Edit button is hidden.
|
|
19
|
+
*/
|
|
20
|
+
export function BlockNodeView(props) {
|
|
21
|
+
const { editor, node, getPos, deleteNode } = props;
|
|
22
|
+
const blockType = String(node.attrs['blockType'] ?? '');
|
|
23
|
+
const blockData = node.attrs['blockData'] ?? {};
|
|
24
|
+
// Tiptap mounts NodeViews in a separate React tree, so we can't read the
|
|
25
|
+
// block registry through context. Pull it off the extension's options
|
|
26
|
+
// instead — set by RichTextField via BlockNodeExtension.configure({ blocks }).
|
|
27
|
+
const blockExt = editor.extensionManager.extensions.find((e) => e.name === 'pilotiqBlock');
|
|
28
|
+
const blocks = blockExt?.options['blocks'] ?? [];
|
|
29
|
+
const onEdit = blockExt?.options['onEdit'];
|
|
30
|
+
const meta = blocks.find((b) => b.name === blockType);
|
|
31
|
+
// Self-heal: a block with no `blockType` is malformed — almost always
|
|
32
|
+
// means a stale node from a prior buggy insert. Delete it on mount so
|
|
33
|
+
// the editor doesn't get stuck in an unrecoverable state.
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (blockType === '')
|
|
36
|
+
deleteNode();
|
|
37
|
+
}, [blockType, deleteNode]);
|
|
38
|
+
if (!meta) {
|
|
39
|
+
if (blockType === '')
|
|
40
|
+
return null;
|
|
41
|
+
return (_jsxs(NodeViewWrapper, { className: "my-2 rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive", children: ["Unknown block type: ", _jsx("code", { children: blockType })] }));
|
|
42
|
+
}
|
|
43
|
+
const summary = meta.schema
|
|
44
|
+
.map((f) => {
|
|
45
|
+
const v = blockData[f.name];
|
|
46
|
+
return typeof v === 'string' && v ? v : '';
|
|
47
|
+
})
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' · ') || meta.label;
|
|
50
|
+
const handleEdit = () => {
|
|
51
|
+
if (!onEdit)
|
|
52
|
+
return;
|
|
53
|
+
const pos = getPos();
|
|
54
|
+
if (typeof pos !== 'number')
|
|
55
|
+
return;
|
|
56
|
+
onEdit(pos);
|
|
57
|
+
};
|
|
58
|
+
return (_jsx(NodeViewWrapper, { className: "pilotiq-block my-3 rounded-lg border bg-muted/30", children: _jsxs("div", { className: "flex items-start justify-between gap-2 px-3 py-2", children: [_jsxs("button", { type: "button", onClick: handleEdit, disabled: !onEdit, className: "flex items-center gap-2 text-left text-sm disabled:cursor-default", children: [meta.icon && _jsx("span", { "aria-hidden": "true", children: meta.icon }), _jsx("span", { className: "font-medium", children: meta.label }), _jsx("span", { className: "text-xs text-muted-foreground line-clamp-1", children: summary })] }), _jsxs("div", { className: "flex items-center gap-2", children: [onEdit && (_jsx("button", { type: "button", onClick: handleEdit, className: "text-xs text-muted-foreground hover:text-foreground", children: "Edit" })), _jsx("button", { type: "button", onClick: () => deleteNode(), className: "text-xs text-destructive hover:underline", children: "Remove" })] })] }) }));
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=BlockNodeView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockNodeView.js","sourceRoot":"","sources":["../../src/react/BlockNodeView.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/react';
|
|
2
|
+
import type { BlockMeta } from '../Block.js';
|
|
3
|
+
/**
|
|
4
|
+
* Floating right-docked side panel for editing a custom block's schema
|
|
5
|
+
* fields. Mounted by `TiptapEditor` once the user clicks the Edit button
|
|
6
|
+
* on a `pilotiqBlock` NodeView; reads/writes flow through ProseMirror
|
|
7
|
+
* directly (no form submit, no roundtrip).
|
|
8
|
+
*
|
|
9
|
+
* Why a sibling of the NodeView and not the NodeView itself:
|
|
10
|
+
* - NodeViews mount in a separate React tree (Tiptap quirk), so they
|
|
11
|
+
* can't reach pilotiq's `FormFields` renderer or any provider on
|
|
12
|
+
* the host page (Theme, Toaster, etc.). Hosting the panel here in
|
|
13
|
+
* the host's tree gives us the full pilotiq field surface for free.
|
|
14
|
+
*
|
|
15
|
+
* Reads: each field's `defaultValue` is overridden from the block's
|
|
16
|
+
* stored `blockData`. Inputs are uncontrolled (outside `FormStateProvider`,
|
|
17
|
+
* pilotiq's renderers fall back to `defaultValue` automatically).
|
|
18
|
+
*
|
|
19
|
+
* Writes: container-level event delegation on the form element. Every
|
|
20
|
+
* change snapshots the entire form via `new FormData(formEl)` →
|
|
21
|
+
* `parseFormDataToNested` (rebuilds nested arrays/objects from
|
|
22
|
+
* dotted-path inputs like `myrep.0.title`) → `coerceBlockValues`
|
|
23
|
+
* (per-fieldType JSON parse / boolean / number coerce so nested-shape
|
|
24
|
+
* fields round-trip in their canonical wire form). The result is
|
|
25
|
+
* dispatched through `state.tr.setNodeMarkup` on the tracked position.
|
|
26
|
+
* The position is kept fresh by mapping it through every editor
|
|
27
|
+
* transaction so live edits elsewhere in the document don't desync.
|
|
28
|
+
*
|
|
29
|
+
* V2 (2026-05-04 cont'd): nested-shape fields now round-trip cleanly:
|
|
30
|
+
* Repeater (array of subschema rows), Builder (heterogeneous block
|
|
31
|
+
* rows), TagsInput / KeyValue / FileUpload (JSON-encoded hidden
|
|
32
|
+
* inputs), Markdown (plain textarea), and the standard primitives
|
|
33
|
+
* (text / textarea / select / toggle / checkbox / radio / date /
|
|
34
|
+
* datetime / number / slider / color / toggleButtons / checkboxList).
|
|
35
|
+
*/
|
|
36
|
+
export interface BlockSidePanelProps {
|
|
37
|
+
editor: Editor;
|
|
38
|
+
/** Position at open time. Tracked + remapped on every transaction. */
|
|
39
|
+
initialPos: number;
|
|
40
|
+
/** Block type at open time — guards against the user clicking Edit on
|
|
41
|
+
* one block, then someone else's edit replacing it with a different
|
|
42
|
+
* block at the same position. */
|
|
43
|
+
blockType: string;
|
|
44
|
+
blocks: BlockMeta[];
|
|
45
|
+
onClose: () => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function BlockSidePanel({ editor, initialPos, blockType, blocks, onClose, }: BlockSidePanelProps): React.ReactElement | null;
|
|
48
|
+
/**
|
|
49
|
+
* Clamp + sanitize a candidate panel width against this panel's bounds
|
|
50
|
+
* (`[PANEL_WIDTH_MIN, PANEL_WIDTH_MAX]`, default `PANEL_WIDTH_DEFAULT`).
|
|
51
|
+
* Thin wrapper around the shared `clampPanelWidth` helper from
|
|
52
|
+
* `@pilotiq/pilotiq/react` — kept exported with the panel-specific
|
|
53
|
+
* defaults baked in so existing tests + downstream callers don't have
|
|
54
|
+
* to plumb the bounds themselves.
|
|
55
|
+
*/
|
|
56
|
+
export declare function clampPanelWidth(value: unknown): number;
|
|
57
|
+
/**
|
|
58
|
+
* Per-fieldType coerce of a nested values map (built by
|
|
59
|
+
* `parseFormDataToNested`) against the block's schema. Mirrors the
|
|
60
|
+
* server-side `coerceFormValues` at a small subset suitable for the
|
|
61
|
+
* side panel — we only run on top-level block fields plus the immediate
|
|
62
|
+
* children of any Repeater rows / Builder rows.data, which is all the
|
|
63
|
+
* V2 surface needs.
|
|
64
|
+
*
|
|
65
|
+
* Non-coerce passthrough for: text, textarea, select, radio, date,
|
|
66
|
+
* dateTime, email, color, toggleButtons, slug, hidden. (Their wire shape
|
|
67
|
+
* is already a plain string / array of strings.)
|
|
68
|
+
*
|
|
69
|
+
* Coerce branches:
|
|
70
|
+
* - `toggle` / `checkbox`: 'true' / 'false' string → boolean.
|
|
71
|
+
* - `number` / `slider`: parse to Number, null on empty, raw string
|
|
72
|
+
* passthrough on NaN (so a half-typed value isn't lost).
|
|
73
|
+
* - `tagsInput`: JSON-encoded string → string[].
|
|
74
|
+
* - `checkboxList`: JSON-encoded string OR array → string[].
|
|
75
|
+
* - `keyValue`: JSON-encoded string → Record<string, unknown>.
|
|
76
|
+
* - `fileUpload`: single → URL string passthrough; multiple →
|
|
77
|
+
* JSON-encoded string → string[].
|
|
78
|
+
* - `repeater`: each row in the array gets recursive coerce against
|
|
79
|
+
* the field's `template` (the inner field schema definition).
|
|
80
|
+
* - `builder`: each row's `data` gets recursive coerce against the
|
|
81
|
+
* block matching `row.type` from `field.blocks[]`. Unknown block
|
|
82
|
+
* types pass through verbatim — the renderer shows a placeholder
|
|
83
|
+
* and the data round-trips intact across config rollbacks.
|
|
84
|
+
*
|
|
85
|
+
* Exported for unit tests. Pure — no React, no DOM, no editor.
|
|
86
|
+
*/
|
|
87
|
+
export declare function coerceBlockValues(raw: Record<string, unknown>, schema: ReadonlyArray<Record<string, unknown>>): Record<string, unknown>;
|
|
88
|
+
/**
|
|
89
|
+
* Read the resolved field value for a given input event target. Kept
|
|
90
|
+
* for back-compat — V1 used this in the per-event handler. V2 reads
|
|
91
|
+
* the entire form via `FormData` instead, but this helper still maps
|
|
92
|
+
* cleanly onto a single-input read for testing and is exported.
|
|
93
|
+
*
|
|
94
|
+
* String passthrough for the common case; explicit coercion for
|
|
95
|
+
* booleans and numerics so the round-trip into the node attrs preserves
|
|
96
|
+
* shape.
|
|
97
|
+
*/
|
|
98
|
+
export declare function readBlockFieldValue(target: {
|
|
99
|
+
type?: string;
|
|
100
|
+
value: string;
|
|
101
|
+
checked?: boolean;
|
|
102
|
+
}, fieldMeta: {
|
|
103
|
+
fieldType?: unknown;
|
|
104
|
+
}): unknown;
|
|
105
|
+
//# sourceMappingURL=BlockSidePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockSidePanel.d.ts","sourceRoot":"","sources":["../../src/react/BlockSidePanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAO3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAkB5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAK,MAAM,CAAA;IACjB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB;;sCAEkC;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAK,SAAS,EAAE,CAAA;IACtB,OAAO,EAAI,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,UAAU,EACV,SAAS,EACT,MAAM,EACN,OAAO,GACR,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAsLjD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAsFD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAK;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9D,SAAS,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,OAAO,CAYT"}
|