@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Suleiman Shahbari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @pilotiq/tiptap
|
|
2
|
+
|
|
3
|
+
Tiptap rich-text adapter for `@pilotiq/pilotiq`. Adds a `RichTextField` with always-on toolbar, slash menu (`/`), draggable blocks, and a custom-block API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @pilotiq/tiptap \
|
|
7
|
+
@tiptap/core @tiptap/pm @tiptap/react @tiptap/starter-kit @tiptap/suggestion \
|
|
8
|
+
@tiptap/extension-link @tiptap/extension-placeholder \
|
|
9
|
+
@tiptap/extension-underline @tiptap/extension-subscript @tiptap/extension-superscript \
|
|
10
|
+
@tiptap/extension-text-align @tiptap/extension-text-style @tiptap/extension-color \
|
|
11
|
+
@tiptap/extension-highlight @tiptap/extension-image @tiptap/extension-table \
|
|
12
|
+
@tiptap/extension-details
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// Once, in your client entry. Wires the editor for `RichTextField` AND the
|
|
17
|
+
// server-side rich-text renderer used by `TextEntry` / `Table` columns.
|
|
18
|
+
import { registerTiptap } from '@pilotiq/tiptap'
|
|
19
|
+
registerTiptap()
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Render Tiptap content to HTML on the server. Pure function — no DOM,
|
|
24
|
+
// no Tiptap runtime. Safe to call from any server context.
|
|
25
|
+
import { renderRichTextToHtml } from '@pilotiq/tiptap'
|
|
26
|
+
renderRichTextToHtml({ type: 'doc', content: [...] })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { RichTextField, Block, MentionProvider } from '@pilotiq/tiptap'
|
|
31
|
+
|
|
32
|
+
RichTextField.make('body')
|
|
33
|
+
.label('Body')
|
|
34
|
+
.placeholder('Start writing…')
|
|
35
|
+
.toolbarButtons([
|
|
36
|
+
['bold', 'italic', 'underline', 'strike', 'link'],
|
|
37
|
+
['h2', 'h3'],
|
|
38
|
+
['textColor', 'highlight'],
|
|
39
|
+
['bulletList', 'orderedList'],
|
|
40
|
+
['attachFiles', 'table', 'details', 'grid', 'gridDelete'],
|
|
41
|
+
['undo', 'redo'],
|
|
42
|
+
])
|
|
43
|
+
.resizableImages()
|
|
44
|
+
.fileAttachmentsAcceptedFileTypes(['image/*'])
|
|
45
|
+
.fileAttachmentsMaxSize(2_000_000)
|
|
46
|
+
.mergeTags(['firstName', 'company'])
|
|
47
|
+
.mentions([
|
|
48
|
+
MentionProvider.make('@').items([
|
|
49
|
+
{ id: 'sleman', label: 'Sleman' },
|
|
50
|
+
]),
|
|
51
|
+
// Async mentions — server-resolved per keystroke.
|
|
52
|
+
MentionProvider.make('+').itemsUsing(async (query) => {
|
|
53
|
+
const matches = await db.users.search(query, { limit: 10 })
|
|
54
|
+
return matches.map(u => ({ id: u.id, label: u.name }))
|
|
55
|
+
}),
|
|
56
|
+
])
|
|
57
|
+
.blocks([
|
|
58
|
+
Block.make('callout').label('Callout').icon('💡').schema([
|
|
59
|
+
TextField.make('title'),
|
|
60
|
+
TextareaField.make('content').required(),
|
|
61
|
+
]),
|
|
62
|
+
])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`attachFiles` reuses the panel's `UploadAdapter` (`Pilotiq.uploads({ adapter })`); the button is stripped server-side when no adapter is wired. The `table` button inserts a 3×3 table with a header row; while the cursor is inside a table, a floating toolbar with column / row / merge / split / header-toggle / delete buttons sits above it. `mergeTags` adds a "Merge tags" group to the slash menu — each id becomes a `{{ id }}` placeholder substituted at read time via `renderRichTextToHtml(content, { mergeTags })`. Each `MentionProvider` registers its trigger character (`@`, `#`, …) and either a static item list (`.items([…])`) or an async resolver (`.itemsUsing(async (query, ctx) => […])`) — the editor fetches a tiny per-form endpoint for async providers and inlines static items into the field meta.
|
|
66
|
+
|
|
67
|
+
Full reference: [docs/packages/tiptap.md](../../docs/packages/tiptap.md).
|
package/dist/Block.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Field } from '@pilotiq/pilotiq';
|
|
2
|
+
import type { FieldMeta } from '@pilotiq/pilotiq';
|
|
3
|
+
/**
|
|
4
|
+
* JSON-serializable block descriptor sent to the editor renderer.
|
|
5
|
+
*/
|
|
6
|
+
export interface BlockMeta {
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
icon: string | undefined;
|
|
10
|
+
schema: FieldMeta[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builder for a custom block type that can be inserted into a `RichTextField`
|
|
14
|
+
* via the slash menu and rendered inline as an editable form.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* Block.make('callout')
|
|
19
|
+
* .label('Callout')
|
|
20
|
+
* .icon('💡')
|
|
21
|
+
* .schema([
|
|
22
|
+
* TextField.make('title'),
|
|
23
|
+
* TextareaField.make('content').required(),
|
|
24
|
+
* ])
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class Block {
|
|
28
|
+
private _name;
|
|
29
|
+
private _label?;
|
|
30
|
+
private _icon?;
|
|
31
|
+
private _schema;
|
|
32
|
+
protected constructor(name: string);
|
|
33
|
+
static make(name: string): Block;
|
|
34
|
+
/** Display label shown in the slash menu. Defaults to the block name. */
|
|
35
|
+
label(label: string): this;
|
|
36
|
+
/** Emoji or icon string shown in the slash menu. */
|
|
37
|
+
icon(icon: string): this;
|
|
38
|
+
/** Fields rendered inline when this block is inserted. */
|
|
39
|
+
schema(fields: Field[]): this;
|
|
40
|
+
getName(): string;
|
|
41
|
+
getLabel(): string;
|
|
42
|
+
getIcon(): string | undefined;
|
|
43
|
+
getSchema(): readonly Field[];
|
|
44
|
+
/** @internal */
|
|
45
|
+
toMeta(): BlockMeta;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=Block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Block.d.ts","sourceRoot":"","sources":["../src/Block.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAI,MAAM,CAAA;IACd,KAAK,EAAG,MAAM,CAAA;IACd,IAAI,EAAI,MAAM,GAAG,SAAS,CAAA;IAC1B,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,OAAO,CAAc;IAE7B,SAAS,aAAa,IAAI,EAAE,MAAM;IAIlC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK;IAIhC,yEAAyE;IACzE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK1B,oDAAoD;IACpD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,0DAA0D;IAC1D,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI;IAK7B,OAAO,IAAM,MAAM;IACnB,QAAQ,IAAK,MAAM;IACnB,OAAO,IAAM,MAAM,GAAG,SAAS;IAC/B,SAAS,IAAI,SAAS,KAAK,EAAE;IAE7B,gBAAgB;IAChB,MAAM,IAAI,SAAS;CAQpB"}
|
package/dist/Block.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for a custom block type that can be inserted into a `RichTextField`
|
|
3
|
+
* via the slash menu and rendered inline as an editable form.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* Block.make('callout')
|
|
8
|
+
* .label('Callout')
|
|
9
|
+
* .icon('💡')
|
|
10
|
+
* .schema([
|
|
11
|
+
* TextField.make('title'),
|
|
12
|
+
* TextareaField.make('content').required(),
|
|
13
|
+
* ])
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class Block {
|
|
17
|
+
_name;
|
|
18
|
+
_label;
|
|
19
|
+
_icon;
|
|
20
|
+
_schema = [];
|
|
21
|
+
constructor(name) {
|
|
22
|
+
this._name = name;
|
|
23
|
+
}
|
|
24
|
+
static make(name) {
|
|
25
|
+
return new Block(name);
|
|
26
|
+
}
|
|
27
|
+
/** Display label shown in the slash menu. Defaults to the block name. */
|
|
28
|
+
label(label) {
|
|
29
|
+
this._label = label;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
/** Emoji or icon string shown in the slash menu. */
|
|
33
|
+
icon(icon) {
|
|
34
|
+
this._icon = icon;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/** Fields rendered inline when this block is inserted. */
|
|
38
|
+
schema(fields) {
|
|
39
|
+
this._schema = fields;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
getName() { return this._name; }
|
|
43
|
+
getLabel() { return this._label ?? this._name; }
|
|
44
|
+
getIcon() { return this._icon; }
|
|
45
|
+
getSchema() { return this._schema; }
|
|
46
|
+
/** @internal */
|
|
47
|
+
toMeta() {
|
|
48
|
+
return {
|
|
49
|
+
name: this._name,
|
|
50
|
+
label: this._label ?? this._name,
|
|
51
|
+
icon: this._icon,
|
|
52
|
+
schema: this._schema.map((f) => f.toMeta()),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=Block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Block.js","sourceRoot":"","sources":["../src/Block.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,KAAK;IACR,KAAK,CAAU;IACf,MAAM,CAAS;IACf,KAAK,CAAU;IACf,OAAO,GAAY,EAAE,CAAA;IAE7B,YAAsB,IAAY;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,IAAY;QACtB,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAA;IACxB,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,KAAa;QACjB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,oDAAoD;IACpD,IAAI,CAAC,IAAY;QACf,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,0DAA0D;IAC1D,MAAM,CAAC,MAAe;QACpB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAgB,OAAO,IAAI,CAAC,KAAK,CAAA,CAAC,CAAC;IAC1C,QAAQ,KAAe,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAA,CAAC,CAAC;IACzD,OAAO,KAA2B,OAAO,IAAI,CAAC,KAAK,CAAA,CAAC,CAAC;IACrD,SAAS,KAAuB,OAAO,IAAI,CAAC,OAAO,CAAA,CAAC,CAAC;IAErD,gBAAgB;IAChB,MAAM;QACJ,OAAO;YACL,IAAI,EAAI,IAAI,CAAC,KAAK;YAClB,KAAK,EAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK;YACjC,IAAI,EAAI,IAAI,CAAC,KAAK;YAClB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAe,CAAC;SACzD,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static menu item surfaced by a {@link MentionProvider}. `id` is the wire
|
|
3
|
+
* identifier that survives serialization; `label` is what the editor shows
|
|
4
|
+
* after the trigger char (`@Sleman`). `group` is purely cosmetic — the menu
|
|
5
|
+
* uses it to bucket items under headings.
|
|
6
|
+
*/
|
|
7
|
+
export interface MentionItem {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
group?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Wire-side shape of a {@link MentionProvider}. */
|
|
13
|
+
export interface MentionProviderMeta {
|
|
14
|
+
trigger: string;
|
|
15
|
+
items: MentionItem[];
|
|
16
|
+
/** Set when the provider is backed by an `itemsUsing(fn)` resolver — the
|
|
17
|
+
* client fetches items from the field's `mentionsUrl` instead of using
|
|
18
|
+
* the (empty) inlined list. */
|
|
19
|
+
async?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Context handed to `MentionProvider.itemsUsing(fn)` resolvers. Mirrors the
|
|
23
|
+
* subset of {@link RenderContext} the route handler can re-derive at request
|
|
24
|
+
* time — `user` from `Pilotiq.user(req=>…)`, `record` for edit-mode forms,
|
|
25
|
+
* and the raw `request` for adapters that need cookie / header access.
|
|
26
|
+
*/
|
|
27
|
+
export interface MentionResolverContext {
|
|
28
|
+
user?: unknown;
|
|
29
|
+
record?: unknown;
|
|
30
|
+
request?: unknown;
|
|
31
|
+
}
|
|
32
|
+
export type MentionItemsResolver = (query: string, ctx: MentionResolverContext) => MentionItem[] | Promise<MentionItem[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Builder for a single mention provider — the trigger character (`@` /
|
|
35
|
+
* `#` / …) and the items the popover offers when the user types it.
|
|
36
|
+
*
|
|
37
|
+
* Two shapes:
|
|
38
|
+
* - `MentionProvider.make('@').items([...])` — static items declared at
|
|
39
|
+
* form-build time, inlined into the field meta.
|
|
40
|
+
* - `MentionProvider.make('@').itemsUsing(async (query, ctx) => […])` —
|
|
41
|
+
* async resolver. The client fetches every keystroke; the server runs
|
|
42
|
+
* the user fn and returns the matched items. `items` and `itemsUsing`
|
|
43
|
+
* are mutually exclusive — last call wins, with a warning when both
|
|
44
|
+
* have been set.
|
|
45
|
+
*
|
|
46
|
+
* Read-time label resolution still works through `renderRichTextToHtml(
|
|
47
|
+
* content, { resolveMention })` for cases where the cached label has gone
|
|
48
|
+
* stale.
|
|
49
|
+
*
|
|
50
|
+
* @example Static items
|
|
51
|
+
* ```ts
|
|
52
|
+
* MentionProvider.make('@').items([
|
|
53
|
+
* { id: 'sleman', label: 'Sleman' },
|
|
54
|
+
* { id: 'alex', label: 'Alex' },
|
|
55
|
+
* ])
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example Async items
|
|
59
|
+
* ```ts
|
|
60
|
+
* MentionProvider.make('@').itemsUsing(async (query) => {
|
|
61
|
+
* const users = await db.users.search(query, { limit: 10 })
|
|
62
|
+
* return users.map(u => ({ id: u.id, label: u.name }))
|
|
63
|
+
* })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare class MentionProvider {
|
|
67
|
+
private _trigger;
|
|
68
|
+
private _items;
|
|
69
|
+
private _itemsUsing?;
|
|
70
|
+
protected constructor(trigger: string);
|
|
71
|
+
static make(trigger: string): MentionProvider;
|
|
72
|
+
/** Replace the static item list. Mutually exclusive with `itemsUsing()`. */
|
|
73
|
+
items(items: MentionItem[]): this;
|
|
74
|
+
/**
|
|
75
|
+
* Install an async resolver. Called every keystroke with the current
|
|
76
|
+
* query (the text after the trigger char) and a `MentionResolverContext`.
|
|
77
|
+
*
|
|
78
|
+
* Mutually exclusive with `items()` — the last call wins; a warning fires
|
|
79
|
+
* when the previously-set static items are dropped silently.
|
|
80
|
+
*/
|
|
81
|
+
itemsUsing(fn: MentionItemsResolver): this;
|
|
82
|
+
getTrigger(): string;
|
|
83
|
+
getItems(): readonly MentionItem[];
|
|
84
|
+
isAsync(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Run the resolver — `itemsUsing(fn)` when set, otherwise the cached
|
|
87
|
+
* static list. Wraps non-array returns in `[]` so a misbehaving
|
|
88
|
+
* resolver doesn't crash the route handler.
|
|
89
|
+
*
|
|
90
|
+
* Synchronous resolvers are awaited the same way as async ones; the
|
|
91
|
+
* single code path keeps the call-site cheap.
|
|
92
|
+
*/
|
|
93
|
+
runResolver(query: string, ctx: MentionResolverContext): Promise<MentionItem[]>;
|
|
94
|
+
/** @internal */
|
|
95
|
+
toMeta(): MentionProviderMeta;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=MentionProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MentionProvider.d.ts","sourceRoot":"","sources":["../src/MentionProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAM,MAAM,CAAA;IACd,KAAK,EAAG,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAI,WAAW,EAAE,CAAA;IACtB;;oCAEgC;IAChC,KAAK,CAAC,EAAG,OAAO,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAK,OAAO,CAAA;IACjB,MAAM,CAAC,EAAG,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,MAAM,oBAAoB,GAAG,CACjC,KAAK,EAAE,MAAM,EACb,GAAG,EAAI,sBAAsB,KAC1B,WAAW,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAsB;IAE1C,SAAS,aAAa,OAAO,EAAE,MAAM;IAOrC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IAI7C,4EAA4E;IAC5E,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,IAAI;IAYjC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,EAAE,oBAAoB,GAAG,IAAI;IAY1C,UAAU,IAAI,MAAM;IACpB,QAAQ,IAAM,SAAS,WAAW,EAAE;IACpC,OAAO,IAAO,OAAO;IAErB;;;;;;;OAOG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWrF,gBAAgB;IAChB,MAAM,IAAI,mBAAmB;CAQ9B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for a single mention provider — the trigger character (`@` /
|
|
3
|
+
* `#` / …) and the items the popover offers when the user types it.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes:
|
|
6
|
+
* - `MentionProvider.make('@').items([...])` — static items declared at
|
|
7
|
+
* form-build time, inlined into the field meta.
|
|
8
|
+
* - `MentionProvider.make('@').itemsUsing(async (query, ctx) => […])` —
|
|
9
|
+
* async resolver. The client fetches every keystroke; the server runs
|
|
10
|
+
* the user fn and returns the matched items. `items` and `itemsUsing`
|
|
11
|
+
* are mutually exclusive — last call wins, with a warning when both
|
|
12
|
+
* have been set.
|
|
13
|
+
*
|
|
14
|
+
* Read-time label resolution still works through `renderRichTextToHtml(
|
|
15
|
+
* content, { resolveMention })` for cases where the cached label has gone
|
|
16
|
+
* stale.
|
|
17
|
+
*
|
|
18
|
+
* @example Static items
|
|
19
|
+
* ```ts
|
|
20
|
+
* MentionProvider.make('@').items([
|
|
21
|
+
* { id: 'sleman', label: 'Sleman' },
|
|
22
|
+
* { id: 'alex', label: 'Alex' },
|
|
23
|
+
* ])
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Async items
|
|
27
|
+
* ```ts
|
|
28
|
+
* MentionProvider.make('@').itemsUsing(async (query) => {
|
|
29
|
+
* const users = await db.users.search(query, { limit: 10 })
|
|
30
|
+
* return users.map(u => ({ id: u.id, label: u.name }))
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class MentionProvider {
|
|
35
|
+
_trigger;
|
|
36
|
+
_items = [];
|
|
37
|
+
_itemsUsing;
|
|
38
|
+
constructor(trigger) {
|
|
39
|
+
if (typeof trigger !== 'string' || trigger.length !== 1) {
|
|
40
|
+
throw new Error(`MentionProvider trigger must be a single character (got ${JSON.stringify(trigger)})`);
|
|
41
|
+
}
|
|
42
|
+
this._trigger = trigger;
|
|
43
|
+
}
|
|
44
|
+
static make(trigger) {
|
|
45
|
+
return new MentionProvider(trigger);
|
|
46
|
+
}
|
|
47
|
+
/** Replace the static item list. Mutually exclusive with `itemsUsing()`. */
|
|
48
|
+
items(items) {
|
|
49
|
+
if (this._itemsUsing !== undefined) {
|
|
50
|
+
console.warn(`[pilotiq/tiptap] MentionProvider('${this._trigger}'): items() called after ` +
|
|
51
|
+
`itemsUsing(). The static list now wins; clear itemsUsing first to avoid surprise.`);
|
|
52
|
+
delete this._itemsUsing;
|
|
53
|
+
}
|
|
54
|
+
this._items = items;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Install an async resolver. Called every keystroke with the current
|
|
59
|
+
* query (the text after the trigger char) and a `MentionResolverContext`.
|
|
60
|
+
*
|
|
61
|
+
* Mutually exclusive with `items()` — the last call wins; a warning fires
|
|
62
|
+
* when the previously-set static items are dropped silently.
|
|
63
|
+
*/
|
|
64
|
+
itemsUsing(fn) {
|
|
65
|
+
if (this._items.length > 0) {
|
|
66
|
+
console.warn(`[pilotiq/tiptap] MentionProvider('${this._trigger}'): itemsUsing() called after ` +
|
|
67
|
+
`items(). The async resolver now wins; the static items array will be ignored.`);
|
|
68
|
+
this._items = [];
|
|
69
|
+
}
|
|
70
|
+
this._itemsUsing = fn;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
getTrigger() { return this._trigger; }
|
|
74
|
+
getItems() { return this._items; }
|
|
75
|
+
isAsync() { return this._itemsUsing !== undefined; }
|
|
76
|
+
/**
|
|
77
|
+
* Run the resolver — `itemsUsing(fn)` when set, otherwise the cached
|
|
78
|
+
* static list. Wraps non-array returns in `[]` so a misbehaving
|
|
79
|
+
* resolver doesn't crash the route handler.
|
|
80
|
+
*
|
|
81
|
+
* Synchronous resolvers are awaited the same way as async ones; the
|
|
82
|
+
* single code path keeps the call-site cheap.
|
|
83
|
+
*/
|
|
84
|
+
async runResolver(query, ctx) {
|
|
85
|
+
if (this._itemsUsing === undefined) {
|
|
86
|
+
// Static path mirrors the client-side filter so the server endpoint
|
|
87
|
+
// would be useful for static providers too — but the client never
|
|
88
|
+
// calls it for them (no `async: true` flag → no fetch).
|
|
89
|
+
return [...this._items];
|
|
90
|
+
}
|
|
91
|
+
const result = await this._itemsUsing(query, ctx);
|
|
92
|
+
return Array.isArray(result) ? result : [];
|
|
93
|
+
}
|
|
94
|
+
/** @internal */
|
|
95
|
+
toMeta() {
|
|
96
|
+
if (this._itemsUsing !== undefined) {
|
|
97
|
+
// Async providers ship empty `items`; the client checks `async: true`
|
|
98
|
+
// and fetches from the field's `mentionsUrl` per-keystroke instead.
|
|
99
|
+
return { trigger: this._trigger, items: [], async: true };
|
|
100
|
+
}
|
|
101
|
+
return { trigger: this._trigger, items: [...this._items] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=MentionProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MentionProvider.js","sourceRoot":"","sources":["../src/MentionProvider.ts"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,OAAO,eAAe;IAClB,QAAQ,CAAQ;IAChB,MAAM,GAAoB,EAAE,CAAA;IAC5B,WAAW,CAAuB;IAE1C,YAAsB,OAAe;QACnC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,2DAA2D,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACxG,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAe;QACzB,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IACrC,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,KAAoB;QACxB,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,CAAC,IAAI,CACV,qCAAqC,IAAI,CAAC,QAAQ,2BAA2B;gBAC7E,mFAAmF,CACpF,CAAA;YACD,OAAO,IAAI,CAAC,WAAW,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,EAAwB;QACjC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,IAAI,CACV,qCAAqC,IAAI,CAAC,QAAQ,gCAAgC;gBAClF,+EAA+E,CAChF,CAAA;YACD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAClB,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,EAAE,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,UAAU,KAAa,OAAO,IAAI,CAAC,QAAQ,CAAA,CAAC,CAAC;IAC7C,QAAQ,KAA+B,OAAO,IAAI,CAAC,MAAM,CAAA,CAAC,CAAC;IAC3D,OAAO,KAAiB,OAAO,IAAI,CAAC,WAAW,KAAK,SAAS,CAAA,CAAC,CAAC;IAE/D;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,GAA2B;QAC1D,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACnC,oEAAoE;YACpE,kEAAkE;YAClE,wDAAwD;YACxD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QACzB,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QACjD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5C,CAAC;IAED,gBAAgB;IAChB,MAAM;QACJ,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACnC,sEAAsE;YACtE,oEAAoE;YACpE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAC3D,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAA;IAC5D,CAAC;CACF"}
|