@rtif-sdk/web 1.0.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-drag-handler.d.ts +189 -0
- package/dist/block-drag-handler.d.ts.map +1 -0
- package/dist/block-drag-handler.js +745 -0
- package/dist/block-drag-handler.js.map +1 -0
- package/dist/block-renderer.d.ts +402 -0
- package/dist/block-renderer.d.ts.map +1 -0
- package/dist/block-renderer.js +424 -0
- package/dist/block-renderer.js.map +1 -0
- package/dist/clipboard.d.ts +178 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +432 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/command-bus.d.ts +113 -0
- package/dist/command-bus.d.ts.map +1 -0
- package/dist/command-bus.js +70 -0
- package/dist/command-bus.js.map +1 -0
- package/dist/composition.d.ts +220 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +271 -0
- package/dist/composition.js.map +1 -0
- package/dist/content-extraction.d.ts +69 -0
- package/dist/content-extraction.d.ts.map +1 -0
- package/dist/content-extraction.js +228 -0
- package/dist/content-extraction.js.map +1 -0
- package/dist/content-handler-file.d.ts +40 -0
- package/dist/content-handler-file.d.ts.map +1 -0
- package/dist/content-handler-file.js +91 -0
- package/dist/content-handler-file.js.map +1 -0
- package/dist/content-handler-image.d.ts +82 -0
- package/dist/content-handler-image.d.ts.map +1 -0
- package/dist/content-handler-image.js +120 -0
- package/dist/content-handler-image.js.map +1 -0
- package/dist/content-handler-url.d.ts +129 -0
- package/dist/content-handler-url.d.ts.map +1 -0
- package/dist/content-handler-url.js +244 -0
- package/dist/content-handler-url.js.map +1 -0
- package/dist/content-handlers.d.ts +67 -0
- package/dist/content-handlers.d.ts.map +1 -0
- package/dist/content-handlers.js +263 -0
- package/dist/content-handlers.js.map +1 -0
- package/dist/content-pipeline.d.ts +383 -0
- package/dist/content-pipeline.d.ts.map +1 -0
- package/dist/content-pipeline.js +232 -0
- package/dist/content-pipeline.js.map +1 -0
- package/dist/cursor-nav.d.ts +149 -0
- package/dist/cursor-nav.d.ts.map +1 -0
- package/dist/cursor-nav.js +230 -0
- package/dist/cursor-nav.js.map +1 -0
- package/dist/cursor-rect.d.ts +65 -0
- package/dist/cursor-rect.d.ts.map +1 -0
- package/dist/cursor-rect.js +98 -0
- package/dist/cursor-rect.js.map +1 -0
- package/dist/drop-indicator.d.ts +108 -0
- package/dist/drop-indicator.d.ts.map +1 -0
- package/dist/drop-indicator.js +236 -0
- package/dist/drop-indicator.js.map +1 -0
- package/dist/editor.d.ts +41 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +710 -0
- package/dist/editor.js.map +1 -0
- package/dist/floating-toolbar.d.ts +93 -0
- package/dist/floating-toolbar.d.ts.map +1 -0
- package/dist/floating-toolbar.js +159 -0
- package/dist/floating-toolbar.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/input-bridge.d.ts +273 -0
- package/dist/input-bridge.d.ts.map +1 -0
- package/dist/input-bridge.js +884 -0
- package/dist/input-bridge.js.map +1 -0
- package/dist/link-popover.d.ts +38 -0
- package/dist/link-popover.d.ts.map +1 -0
- package/dist/link-popover.js +278 -0
- package/dist/link-popover.js.map +1 -0
- package/dist/mark-renderer.d.ts +275 -0
- package/dist/mark-renderer.d.ts.map +1 -0
- package/dist/mark-renderer.js +210 -0
- package/dist/mark-renderer.js.map +1 -0
- package/dist/perf.d.ts +145 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +260 -0
- package/dist/perf.js.map +1 -0
- package/dist/plugin-kit.d.ts +265 -0
- package/dist/plugin-kit.d.ts.map +1 -0
- package/dist/plugin-kit.js +234 -0
- package/dist/plugin-kit.js.map +1 -0
- package/dist/plugins/alignment-plugin.d.ts +68 -0
- package/dist/plugins/alignment-plugin.d.ts.map +1 -0
- package/dist/plugins/alignment-plugin.js +98 -0
- package/dist/plugins/alignment-plugin.js.map +1 -0
- package/dist/plugins/block-utils.d.ts +113 -0
- package/dist/plugins/block-utils.d.ts.map +1 -0
- package/dist/plugins/block-utils.js +191 -0
- package/dist/plugins/block-utils.js.map +1 -0
- package/dist/plugins/blockquote-plugin.d.ts +39 -0
- package/dist/plugins/blockquote-plugin.d.ts.map +1 -0
- package/dist/plugins/blockquote-plugin.js +88 -0
- package/dist/plugins/blockquote-plugin.js.map +1 -0
- package/dist/plugins/bold-plugin.d.ts +37 -0
- package/dist/plugins/bold-plugin.d.ts.map +1 -0
- package/dist/plugins/bold-plugin.js +48 -0
- package/dist/plugins/bold-plugin.js.map +1 -0
- package/dist/plugins/callout-plugin.d.ts +100 -0
- package/dist/plugins/callout-plugin.d.ts.map +1 -0
- package/dist/plugins/callout-plugin.js +200 -0
- package/dist/plugins/callout-plugin.js.map +1 -0
- package/dist/plugins/code-block-plugin.d.ts +62 -0
- package/dist/plugins/code-block-plugin.d.ts.map +1 -0
- package/dist/plugins/code-block-plugin.js +176 -0
- package/dist/plugins/code-block-plugin.js.map +1 -0
- package/dist/plugins/code-plugin.d.ts +37 -0
- package/dist/plugins/code-plugin.d.ts.map +1 -0
- package/dist/plugins/code-plugin.js +48 -0
- package/dist/plugins/code-plugin.js.map +1 -0
- package/dist/plugins/embed-plugin.d.ts +90 -0
- package/dist/plugins/embed-plugin.d.ts.map +1 -0
- package/dist/plugins/embed-plugin.js +147 -0
- package/dist/plugins/embed-plugin.js.map +1 -0
- package/dist/plugins/font-family-plugin.d.ts +58 -0
- package/dist/plugins/font-family-plugin.d.ts.map +1 -0
- package/dist/plugins/font-family-plugin.js +57 -0
- package/dist/plugins/font-family-plugin.js.map +1 -0
- package/dist/plugins/font-size-plugin.d.ts +57 -0
- package/dist/plugins/font-size-plugin.d.ts.map +1 -0
- package/dist/plugins/font-size-plugin.js +56 -0
- package/dist/plugins/font-size-plugin.js.map +1 -0
- package/dist/plugins/heading-plugin.d.ts +52 -0
- package/dist/plugins/heading-plugin.d.ts.map +1 -0
- package/dist/plugins/heading-plugin.js +114 -0
- package/dist/plugins/heading-plugin.js.map +1 -0
- package/dist/plugins/hr-plugin.d.ts +33 -0
- package/dist/plugins/hr-plugin.d.ts.map +1 -0
- package/dist/plugins/hr-plugin.js +75 -0
- package/dist/plugins/hr-plugin.js.map +1 -0
- package/dist/plugins/image-plugin.d.ts +115 -0
- package/dist/plugins/image-plugin.d.ts.map +1 -0
- package/dist/plugins/image-plugin.js +199 -0
- package/dist/plugins/image-plugin.js.map +1 -0
- package/dist/plugins/indent-plugin.d.ts +62 -0
- package/dist/plugins/indent-plugin.d.ts.map +1 -0
- package/dist/plugins/indent-plugin.js +128 -0
- package/dist/plugins/indent-plugin.js.map +1 -0
- package/dist/plugins/index.d.ts +45 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +42 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/italic-plugin.d.ts +37 -0
- package/dist/plugins/italic-plugin.d.ts.map +1 -0
- package/dist/plugins/italic-plugin.js +48 -0
- package/dist/plugins/italic-plugin.js.map +1 -0
- package/dist/plugins/link-plugin.d.ts +129 -0
- package/dist/plugins/link-plugin.d.ts.map +1 -0
- package/dist/plugins/link-plugin.js +212 -0
- package/dist/plugins/link-plugin.js.map +1 -0
- package/dist/plugins/list-plugin.d.ts +53 -0
- package/dist/plugins/list-plugin.d.ts.map +1 -0
- package/dist/plugins/list-plugin.js +309 -0
- package/dist/plugins/list-plugin.js.map +1 -0
- package/dist/plugins/mark-utils.d.ts +173 -0
- package/dist/plugins/mark-utils.d.ts.map +1 -0
- package/dist/plugins/mark-utils.js +425 -0
- package/dist/plugins/mark-utils.js.map +1 -0
- package/dist/plugins/mention-plugin.d.ts +191 -0
- package/dist/plugins/mention-plugin.d.ts.map +1 -0
- package/dist/plugins/mention-plugin.js +295 -0
- package/dist/plugins/mention-plugin.js.map +1 -0
- package/dist/plugins/strikethrough-plugin.d.ts +37 -0
- package/dist/plugins/strikethrough-plugin.d.ts.map +1 -0
- package/dist/plugins/strikethrough-plugin.js +48 -0
- package/dist/plugins/strikethrough-plugin.js.map +1 -0
- package/dist/plugins/text-color-plugin.d.ts +57 -0
- package/dist/plugins/text-color-plugin.d.ts.map +1 -0
- package/dist/plugins/text-color-plugin.js +56 -0
- package/dist/plugins/text-color-plugin.js.map +1 -0
- package/dist/plugins/underline-plugin.d.ts +37 -0
- package/dist/plugins/underline-plugin.d.ts.map +1 -0
- package/dist/plugins/underline-plugin.js +48 -0
- package/dist/plugins/underline-plugin.js.map +1 -0
- package/dist/presets.d.ts +95 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +159 -0
- package/dist/presets.js.map +1 -0
- package/dist/renderer.d.ts +125 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +415 -0
- package/dist/renderer.js.map +1 -0
- package/dist/scroll-to-cursor.d.ts +25 -0
- package/dist/scroll-to-cursor.d.ts.map +1 -0
- package/dist/scroll-to-cursor.js +59 -0
- package/dist/scroll-to-cursor.js.map +1 -0
- package/dist/selection-sync.d.ts +159 -0
- package/dist/selection-sync.d.ts.map +1 -0
- package/dist/selection-sync.js +527 -0
- package/dist/selection-sync.js.map +1 -0
- package/dist/shortcut-handler.d.ts +98 -0
- package/dist/shortcut-handler.d.ts.map +1 -0
- package/dist/shortcut-handler.js +155 -0
- package/dist/shortcut-handler.js.map +1 -0
- package/dist/toolbar.d.ts +103 -0
- package/dist/toolbar.d.ts.map +1 -0
- package/dist/toolbar.js +134 -0
- package/dist/toolbar.js.map +1 -0
- package/dist/trigger-manager.d.ts +205 -0
- package/dist/trigger-manager.d.ts.map +1 -0
- package/dist/trigger-manager.js +466 -0
- package/dist/trigger-manager.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in content handlers for the RTIF content pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Provides handlers for:
|
|
5
|
+
* - Plain text (`text/plain`) — splits on newlines, creates blocks
|
|
6
|
+
* - RTIF JSON (`application/x-rtif+json`) — preserves marks and block attrs
|
|
7
|
+
* - HTML (`text/html`) — stub, falls through to plain text handler
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Plain text handler
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Create a content handler for plain text paste/drop.
|
|
16
|
+
*
|
|
17
|
+
* Accepts `text/plain` content and converts it to RTIF operations by splitting
|
|
18
|
+
* on newlines. Each line becomes a separate block via `split_block`, with the
|
|
19
|
+
* text inserted via `insert_text`.
|
|
20
|
+
*
|
|
21
|
+
* Priority: -100 (lowest built-in, runs last as fallback).
|
|
22
|
+
*
|
|
23
|
+
* @param generateBlockId - Factory for generating unique block IDs
|
|
24
|
+
* @returns A ContentHandler for plain text
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const handler = createPlainTextHandler(() => crypto.randomUUID());
|
|
29
|
+
* pipeline.register(handler);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createPlainTextHandler(generateBlockId) {
|
|
33
|
+
return {
|
|
34
|
+
id: 'rtif:plain-text',
|
|
35
|
+
accept: ['text/plain'],
|
|
36
|
+
priority: -100,
|
|
37
|
+
async handle(item, context) {
|
|
38
|
+
const text = await item.getString();
|
|
39
|
+
if (text === null || text === '')
|
|
40
|
+
return false;
|
|
41
|
+
let insertOffset = context.deleteSelectionAndGetOffset();
|
|
42
|
+
const ops = [];
|
|
43
|
+
const lines = text.split('\n');
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i];
|
|
46
|
+
if (i > 0) {
|
|
47
|
+
ops.push({
|
|
48
|
+
type: 'split_block',
|
|
49
|
+
offset: insertOffset,
|
|
50
|
+
newBlockId: generateBlockId(),
|
|
51
|
+
});
|
|
52
|
+
// After split_block, offset advances by 1 for the virtual \n
|
|
53
|
+
insertOffset += 1;
|
|
54
|
+
}
|
|
55
|
+
if (line.length > 0) {
|
|
56
|
+
ops.push({
|
|
57
|
+
type: 'insert_text',
|
|
58
|
+
offset: insertOffset,
|
|
59
|
+
text: line,
|
|
60
|
+
});
|
|
61
|
+
insertOffset += line.length;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (ops.length > 0) {
|
|
65
|
+
context.dispatch(ops);
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a content handler for RTIF JSON paste.
|
|
73
|
+
*
|
|
74
|
+
* Accepts `application/x-rtif+json` content and preserves full formatting
|
|
75
|
+
* fidelity including span marks and block attributes. Parses the JSON payload,
|
|
76
|
+
* validates its structure, and generates the appropriate RTIF operations.
|
|
77
|
+
*
|
|
78
|
+
* Priority: -80 (highest built-in, runs before HTML and plain text).
|
|
79
|
+
*
|
|
80
|
+
* @param generateBlockId - Factory for generating unique block IDs
|
|
81
|
+
* @returns A ContentHandler for RTIF JSON paste
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const handler = createRtifPasteHandler(() => crypto.randomUUID());
|
|
86
|
+
* pipeline.register(handler);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function createRtifPasteHandler(generateBlockId) {
|
|
90
|
+
return {
|
|
91
|
+
id: 'rtif:rtif-json',
|
|
92
|
+
accept: ['application/x-rtif+json'],
|
|
93
|
+
priority: -80,
|
|
94
|
+
async handle(item, context) {
|
|
95
|
+
const jsonStr = await item.getString();
|
|
96
|
+
if (jsonStr === null || jsonStr === '')
|
|
97
|
+
return false;
|
|
98
|
+
// Parse and validate
|
|
99
|
+
const payload = parseRtifPayload(jsonStr);
|
|
100
|
+
if (payload === null)
|
|
101
|
+
return false;
|
|
102
|
+
let insertOffset = context.deleteSelectionAndGetOffset();
|
|
103
|
+
const ops = [];
|
|
104
|
+
// Track the offsets where each new block starts, for set_span_marks later
|
|
105
|
+
const blockInsertions = [];
|
|
106
|
+
// Phase 1: Insert text and create blocks
|
|
107
|
+
for (let bi = 0; bi < payload.blocks.length; bi++) {
|
|
108
|
+
const block = payload.blocks[bi];
|
|
109
|
+
const isFirst = bi === 0;
|
|
110
|
+
let newBlockId = null;
|
|
111
|
+
if (!isFirst) {
|
|
112
|
+
newBlockId = generateBlockId();
|
|
113
|
+
ops.push({
|
|
114
|
+
type: 'split_block',
|
|
115
|
+
offset: insertOffset,
|
|
116
|
+
newBlockId,
|
|
117
|
+
});
|
|
118
|
+
insertOffset += 1;
|
|
119
|
+
}
|
|
120
|
+
const blockStartOffset = insertOffset;
|
|
121
|
+
// Insert the full text of all spans in this block
|
|
122
|
+
const fullText = block.spans.map((s) => s.text).join('');
|
|
123
|
+
if (fullText.length > 0) {
|
|
124
|
+
ops.push({
|
|
125
|
+
type: 'insert_text',
|
|
126
|
+
offset: insertOffset,
|
|
127
|
+
text: fullText,
|
|
128
|
+
});
|
|
129
|
+
insertOffset += fullText.length;
|
|
130
|
+
}
|
|
131
|
+
blockInsertions.push({
|
|
132
|
+
blockStartOffset,
|
|
133
|
+
block,
|
|
134
|
+
isFirstBlock: isFirst,
|
|
135
|
+
newBlockId,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Phase 2: Apply span marks for each block
|
|
139
|
+
for (const { blockStartOffset, block } of blockInsertions) {
|
|
140
|
+
let spanOffset = blockStartOffset;
|
|
141
|
+
for (const span of block.spans) {
|
|
142
|
+
if (span.marks && Object.keys(span.marks).length > 0) {
|
|
143
|
+
ops.push({
|
|
144
|
+
type: 'set_span_marks',
|
|
145
|
+
offset: spanOffset,
|
|
146
|
+
count: span.text.length,
|
|
147
|
+
marks: span.marks,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
spanOffset += span.text.length;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Phase 3: Apply block attrs (only on NEW blocks created by split, not the original)
|
|
154
|
+
for (const { block, isFirstBlock, newBlockId, } of blockInsertions) {
|
|
155
|
+
if (isFirstBlock)
|
|
156
|
+
continue; // Don't set attrs on the block the cursor was in
|
|
157
|
+
if (!block.attrs || Object.keys(block.attrs).length === 0)
|
|
158
|
+
continue;
|
|
159
|
+
if (newBlockId === null)
|
|
160
|
+
continue;
|
|
161
|
+
ops.push({
|
|
162
|
+
type: 'set_block_attrs',
|
|
163
|
+
blockId: newBlockId,
|
|
164
|
+
attrs: block.attrs,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Phase 4: Apply block type on NEW blocks to match source document
|
|
168
|
+
for (const { block, isFirstBlock, newBlockId, } of blockInsertions) {
|
|
169
|
+
if (isFirstBlock)
|
|
170
|
+
continue; // Don't change the cursor's original block type
|
|
171
|
+
if (newBlockId === null)
|
|
172
|
+
continue;
|
|
173
|
+
ops.push({
|
|
174
|
+
type: 'set_block_type',
|
|
175
|
+
blockId: newBlockId,
|
|
176
|
+
blockType: block.type,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (ops.length > 0) {
|
|
180
|
+
context.dispatch(ops);
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Parse and validate an RTIF clipboard JSON payload.
|
|
188
|
+
*
|
|
189
|
+
* @param jsonStr - The JSON string from the clipboard
|
|
190
|
+
* @returns Parsed payload, or null if invalid
|
|
191
|
+
*/
|
|
192
|
+
function parseRtifPayload(jsonStr) {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(jsonStr);
|
|
195
|
+
if (typeof parsed !== 'object' ||
|
|
196
|
+
parsed === null ||
|
|
197
|
+
!('version' in parsed) ||
|
|
198
|
+
!('blocks' in parsed)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const obj = parsed;
|
|
202
|
+
if (obj['version'] !== 1)
|
|
203
|
+
return null;
|
|
204
|
+
if (!Array.isArray(obj['blocks']))
|
|
205
|
+
return null;
|
|
206
|
+
const blocks = obj['blocks'];
|
|
207
|
+
if (blocks.length === 0)
|
|
208
|
+
return null;
|
|
209
|
+
// Validate each block has at least spans array
|
|
210
|
+
for (const block of blocks) {
|
|
211
|
+
if (typeof block !== 'object' || block === null)
|
|
212
|
+
return null;
|
|
213
|
+
const b = block;
|
|
214
|
+
if (!Array.isArray(b['spans']))
|
|
215
|
+
return null;
|
|
216
|
+
// Validate each span has text
|
|
217
|
+
const spans = b['spans'];
|
|
218
|
+
for (const span of spans) {
|
|
219
|
+
if (typeof span !== 'object' || span === null)
|
|
220
|
+
return null;
|
|
221
|
+
const s = span;
|
|
222
|
+
if (typeof s['text'] !== 'string')
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return parsed;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// HTML paste handler (stub)
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
/**
|
|
236
|
+
* Create a stub content handler for HTML paste.
|
|
237
|
+
*
|
|
238
|
+
* Currently always returns `false` (falls through to the plain text handler).
|
|
239
|
+
* Will be implemented when the `@rtif-sdk/format-html` plugin is available.
|
|
240
|
+
*
|
|
241
|
+
* Priority: -90 (between RTIF JSON and plain text).
|
|
242
|
+
*
|
|
243
|
+
* @returns A ContentHandler stub for HTML paste
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* const handler = createHtmlPasteHandler();
|
|
248
|
+
* pipeline.register(handler);
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function createHtmlPasteHandler() {
|
|
252
|
+
return {
|
|
253
|
+
id: 'rtif:html',
|
|
254
|
+
accept: ['text/html'],
|
|
255
|
+
priority: -90,
|
|
256
|
+
async handle(_item, _context) {
|
|
257
|
+
// Stub — always declines. HTML deserialization will be implemented
|
|
258
|
+
// when @rtif-sdk/format-html is available.
|
|
259
|
+
return false;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=content-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-handlers.js","sourceRoot":"","sources":["../src/content-handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content pipeline — unified ingestion for paste, drop, and programmatic input.
|
|
3
|
+
*
|
|
4
|
+
* The content pipeline routes incoming content items through a priority-ordered
|
|
5
|
+
* chain of handlers. Each handler declares which MIME types it accepts and
|
|
6
|
+
* processes matching items into RTIF operations. All operations from a single
|
|
7
|
+
* process() call are batched into one engine.dispatch() for a single undo group.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
* @see docs/plans/web/03-content-pipeline.md
|
|
11
|
+
*/
|
|
12
|
+
import type { Operation } from '@rtif-sdk/core';
|
|
13
|
+
import type { IEditorEngine } from '@rtif-sdk/engine';
|
|
14
|
+
import type { CommandBus } from './command-bus.js';
|
|
15
|
+
import type { Disposable } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Source of a content item — how the content entered the editor.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const source: ContentItemSource = 'paste';
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export type ContentItemSource = 'paste' | 'drop' | 'input' | 'programmatic';
|
|
25
|
+
/**
|
|
26
|
+
* A single piece of content to be processed by the pipeline.
|
|
27
|
+
*
|
|
28
|
+
* Content items wrap clipboard data, dropped files, or programmatic input.
|
|
29
|
+
* The `type` field is a MIME type string used for handler matching.
|
|
30
|
+
* Data is accessed lazily via async getters to support both sync and async
|
|
31
|
+
* clipboard APIs.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const item: ContentItem = {
|
|
36
|
+
* type: 'text/plain',
|
|
37
|
+
* source: 'paste',
|
|
38
|
+
* getString: async () => 'Hello world',
|
|
39
|
+
* getFile: async () => null,
|
|
40
|
+
* getBlob: async () => null,
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export interface ContentItem {
|
|
45
|
+
/** MIME type of the content (e.g., 'text/plain', 'image/png'). */
|
|
46
|
+
readonly type: string;
|
|
47
|
+
/** How this content entered the editor. */
|
|
48
|
+
readonly source: ContentItemSource;
|
|
49
|
+
/**
|
|
50
|
+
* Get the content as a string.
|
|
51
|
+
*
|
|
52
|
+
* @returns The string content, or null if not available as text
|
|
53
|
+
*/
|
|
54
|
+
getString(): Promise<string | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Get the content as a File.
|
|
57
|
+
*
|
|
58
|
+
* @returns The File object, or null if not a file
|
|
59
|
+
*/
|
|
60
|
+
getFile(): Promise<File | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Get the content as a Blob.
|
|
63
|
+
*
|
|
64
|
+
* @returns The Blob object, or null if not available as a blob
|
|
65
|
+
*/
|
|
66
|
+
getBlob(): Promise<Blob | null>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A handler that processes content items of specific MIME types.
|
|
70
|
+
*
|
|
71
|
+
* Handlers are registered with the pipeline and matched against incoming
|
|
72
|
+
* items by MIME type. Higher-priority handlers run first. The first handler
|
|
73
|
+
* that returns `true` from `handle()` stops all processing.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const handler: ContentHandler = {
|
|
78
|
+
* id: 'plain-text',
|
|
79
|
+
* accept: ['text/plain'],
|
|
80
|
+
* priority: -100,
|
|
81
|
+
* async handle(item, context) {
|
|
82
|
+
* const text = await item.getString();
|
|
83
|
+
* if (!text) return false;
|
|
84
|
+
* context.dispatch([{ type: 'insert_text', offset: context.insertOffset, text }]);
|
|
85
|
+
* return true;
|
|
86
|
+
* },
|
|
87
|
+
* };
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export interface ContentHandler {
|
|
91
|
+
/** Unique identifier for this handler. */
|
|
92
|
+
readonly id: string;
|
|
93
|
+
/**
|
|
94
|
+
* MIME type patterns this handler accepts.
|
|
95
|
+
* Supports exact match ('text/plain'), wildcard subtypes ('image/*'),
|
|
96
|
+
* and catch-all ('*\/*').
|
|
97
|
+
*/
|
|
98
|
+
readonly accept: readonly string[];
|
|
99
|
+
/**
|
|
100
|
+
* Priority for handler ordering. Higher values run first.
|
|
101
|
+
* Built-in handlers use negative values to allow user handlers to take precedence.
|
|
102
|
+
* Default: 0.
|
|
103
|
+
*/
|
|
104
|
+
readonly priority?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Optional pre-check before calling handle().
|
|
107
|
+
* Return false to skip this handler for this item.
|
|
108
|
+
*
|
|
109
|
+
* @param item - The content item to check
|
|
110
|
+
* @param context - The processing context
|
|
111
|
+
* @returns Whether this handler can process the item
|
|
112
|
+
*/
|
|
113
|
+
canHandle?(item: ContentItem, context: ContentContext): Promise<boolean>;
|
|
114
|
+
/**
|
|
115
|
+
* Process a content item.
|
|
116
|
+
*
|
|
117
|
+
* @param item - The content item to process
|
|
118
|
+
* @param context - The processing context with dispatch and helpers
|
|
119
|
+
* @returns true if the item was handled, false to pass to next handler
|
|
120
|
+
*/
|
|
121
|
+
handle(item: ContentItem, context: ContentContext): Promise<boolean>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Context provided to content handlers during processing.
|
|
125
|
+
*
|
|
126
|
+
* Exposes the engine, commands, and helper methods for dispatching operations.
|
|
127
|
+
* All operations dispatched through this context are buffered and flushed as
|
|
128
|
+
* a single batch when processing completes, ensuring a single undo group.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* async handle(item: ContentItem, context: ContentContext): Promise<boolean> {
|
|
133
|
+
* const offset = context.deleteSelectionAndGetOffset();
|
|
134
|
+
* const text = await item.getString();
|
|
135
|
+
* if (!text) return false;
|
|
136
|
+
* context.dispatch([{ type: 'insert_text', offset, text }]);
|
|
137
|
+
* return true;
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export interface ContentContext {
|
|
142
|
+
/** The RTIF engine instance. */
|
|
143
|
+
readonly engine: IEditorEngine;
|
|
144
|
+
/** The command bus for executing commands. */
|
|
145
|
+
readonly commands: CommandBus;
|
|
146
|
+
/** The absolute offset where content will be inserted. */
|
|
147
|
+
readonly insertOffset: number;
|
|
148
|
+
/** How this content entered the editor. */
|
|
149
|
+
readonly source: ContentItemSource;
|
|
150
|
+
/**
|
|
151
|
+
* Delete the current selection (if non-collapsed) and return the insert offset.
|
|
152
|
+
*
|
|
153
|
+
* This is idempotent — calling it multiple times within the same handler
|
|
154
|
+
* only deletes once. Returns the start of the (former) selection range.
|
|
155
|
+
*
|
|
156
|
+
* @returns The absolute offset where content should be inserted
|
|
157
|
+
*/
|
|
158
|
+
deleteSelectionAndGetOffset(): number;
|
|
159
|
+
/**
|
|
160
|
+
* Buffer operations for dispatch.
|
|
161
|
+
*
|
|
162
|
+
* Operations are NOT dispatched immediately. They are collected and flushed
|
|
163
|
+
* as a single `engine.dispatch()` call when the handler returns true.
|
|
164
|
+
*
|
|
165
|
+
* @param ops - Operations to add to the buffer
|
|
166
|
+
*/
|
|
167
|
+
dispatch(ops: Operation[]): void;
|
|
168
|
+
/**
|
|
169
|
+
* Generate a unique block ID for split_block operations.
|
|
170
|
+
*
|
|
171
|
+
* @returns A unique block ID string
|
|
172
|
+
*/
|
|
173
|
+
generateBlockId(): string;
|
|
174
|
+
}
|
|
175
|
+
export type { Disposable } from './types.js';
|
|
176
|
+
/**
|
|
177
|
+
* Emitted before content items are processed.
|
|
178
|
+
* Call `preventDefault()` to cancel processing.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* pipeline.onEvent((event) => {
|
|
183
|
+
* if (event.type === 'contentReceived' && shouldBlock(event.items)) {
|
|
184
|
+
* event.preventDefault();
|
|
185
|
+
* }
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export interface ContentReceivedEvent {
|
|
190
|
+
readonly type: 'contentReceived';
|
|
191
|
+
readonly items: readonly ContentItem[];
|
|
192
|
+
readonly source: ContentItemSource;
|
|
193
|
+
prevented: boolean;
|
|
194
|
+
preventDefault(): void;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Emitted when a handler successfully processes a content item.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* pipeline.onEvent((event) => {
|
|
202
|
+
* if (event.type === 'contentHandled') {
|
|
203
|
+
* console.log(`Handled by ${event.handlerId}`);
|
|
204
|
+
* }
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export interface ContentHandledEvent {
|
|
209
|
+
readonly type: 'contentHandled';
|
|
210
|
+
readonly handlerId: string;
|
|
211
|
+
readonly item: ContentItem;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Emitted when no handler accepted a content item.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* pipeline.onEvent((event) => {
|
|
219
|
+
* if (event.type === 'contentRejected') {
|
|
220
|
+
* console.warn(`No handler for ${event.item.type}: ${event.reason}`);
|
|
221
|
+
* }
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export interface ContentRejectedEvent {
|
|
226
|
+
readonly type: 'contentRejected';
|
|
227
|
+
readonly item: ContentItem;
|
|
228
|
+
readonly reason: 'no-handler' | 'all-declined';
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Union of all content pipeline events.
|
|
232
|
+
*/
|
|
233
|
+
export type ContentPipelineEvent = ContentReceivedEvent | ContentHandledEvent | ContentRejectedEvent;
|
|
234
|
+
/**
|
|
235
|
+
* The content pipeline processes incoming content (paste, drop, programmatic)
|
|
236
|
+
* through a priority-ordered chain of handlers.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const pipeline = createContentPipeline();
|
|
241
|
+
* pipeline.register(createPlainTextHandler(() => generateId()));
|
|
242
|
+
* pipeline.register(createRtifPasteHandler(() => generateId()));
|
|
243
|
+
*
|
|
244
|
+
* const items = extractFromPaste(clipboardEvent);
|
|
245
|
+
* await pipeline.process(items, {
|
|
246
|
+
* engine,
|
|
247
|
+
* commands: commandBus,
|
|
248
|
+
* source: 'paste',
|
|
249
|
+
* generateBlockId: () => generateId(),
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export interface ContentPipeline {
|
|
254
|
+
/**
|
|
255
|
+
* Register a content handler.
|
|
256
|
+
*
|
|
257
|
+
* @param handler - The handler to register
|
|
258
|
+
* @returns A disposable that unregisters the handler when disposed
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const registration = pipeline.register(myHandler);
|
|
263
|
+
* registration.dispose(); // unregister
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
register(handler: ContentHandler): Disposable;
|
|
267
|
+
/**
|
|
268
|
+
* Process an array of content items through the handler chain.
|
|
269
|
+
*
|
|
270
|
+
* Uses handler-first iteration: for each handler (sorted by descending priority),
|
|
271
|
+
* check each item. The first handler+item match that returns true from handle()
|
|
272
|
+
* stops ALL processing. All dispatched operations are flushed as a single
|
|
273
|
+
* engine.dispatch() call.
|
|
274
|
+
*
|
|
275
|
+
* @param items - Content items to process
|
|
276
|
+
* @param contextOpts - Options for creating the processing context
|
|
277
|
+
* @returns true if any handler accepted an item, false otherwise
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* const handled = await pipeline.process(items, {
|
|
282
|
+
* engine,
|
|
283
|
+
* commands: commandBus,
|
|
284
|
+
* source: 'paste',
|
|
285
|
+
* generateBlockId: () => crypto.randomUUID(),
|
|
286
|
+
* });
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
process(items: ContentItem[], contextOpts: ContentContextOpts): Promise<boolean>;
|
|
290
|
+
/**
|
|
291
|
+
* Check whether any registered handler accepts a given MIME type.
|
|
292
|
+
*
|
|
293
|
+
* @param type - MIME type to check
|
|
294
|
+
* @returns true if at least one handler accepts this type
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* if (pipeline.hasHandlerForType('text/html')) { ... }
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
hasHandlerForType(type: string): boolean;
|
|
302
|
+
/**
|
|
303
|
+
* Subscribe to pipeline events.
|
|
304
|
+
*
|
|
305
|
+
* @param listener - Event listener callback
|
|
306
|
+
* @returns Unsubscribe function
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* const unsub = pipeline.onEvent((event) => console.log(event.type));
|
|
311
|
+
* unsub();
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
onEvent(listener: (event: ContentPipelineEvent) => void): () => void;
|
|
315
|
+
/**
|
|
316
|
+
* Destroy the pipeline, removing all handlers and listeners.
|
|
317
|
+
*/
|
|
318
|
+
destroy(): void;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Options for creating a content processing context.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* const opts: ContentContextOpts = {
|
|
326
|
+
* engine,
|
|
327
|
+
* commands: commandBus,
|
|
328
|
+
* source: 'paste',
|
|
329
|
+
* generateBlockId: () => crypto.randomUUID(),
|
|
330
|
+
* };
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
export interface ContentContextOpts {
|
|
334
|
+
/** The RTIF engine instance. */
|
|
335
|
+
readonly engine: IEditorEngine;
|
|
336
|
+
/** The command bus. */
|
|
337
|
+
readonly commands: CommandBus;
|
|
338
|
+
/** How this content entered the editor. */
|
|
339
|
+
readonly source: ContentItemSource;
|
|
340
|
+
/**
|
|
341
|
+
* For drop events, the absolute offset at the drop position.
|
|
342
|
+
* If undefined, uses the current engine selection.
|
|
343
|
+
*/
|
|
344
|
+
readonly dropOffset?: number;
|
|
345
|
+
/** Factory for generating unique block IDs. */
|
|
346
|
+
readonly generateBlockId: () => string;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check whether a MIME type matches any of the given patterns.
|
|
350
|
+
*
|
|
351
|
+
* Supports exact match, wildcard subtypes (e.g., `image/*`), and
|
|
352
|
+
* catch-all (`*\/*`).
|
|
353
|
+
*
|
|
354
|
+
* @param contentType - The MIME type to test (e.g., 'text/plain')
|
|
355
|
+
* @param patterns - Array of MIME patterns to match against
|
|
356
|
+
* @returns true if the content type matches any pattern
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```ts
|
|
360
|
+
* matchesMime('image/png', ['image/*']); // true
|
|
361
|
+
* matchesMime('text/plain', ['text/html']); // false
|
|
362
|
+
* matchesMime('text/plain', ['*\/*']); // true (catch-all)
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
export declare function matchesMime(contentType: string, patterns: readonly string[]): boolean;
|
|
366
|
+
/**
|
|
367
|
+
* Create a content pipeline instance.
|
|
368
|
+
*
|
|
369
|
+
* The pipeline manages a priority-ordered set of content handlers and routes
|
|
370
|
+
* incoming content items through them. All operations dispatched by handlers
|
|
371
|
+
* are buffered and flushed as a single engine.dispatch() call, ensuring each
|
|
372
|
+
* paste/drop is a single undo group.
|
|
373
|
+
*
|
|
374
|
+
* @returns A new ContentPipeline instance
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* const pipeline = createContentPipeline();
|
|
379
|
+
* pipeline.register(createPlainTextHandler(() => generateId()));
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
export declare function createContentPipeline(): ContentPipeline;
|
|
383
|
+
//# sourceMappingURL=content-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-pipeline.d.ts","sourceRoot":"","sources":["../src/content-pipeline.ts"],"names":[],"mappings":"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"}
|