@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,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard handling — copy, cut, and paste for the RTIF web editor.
|
|
3
|
+
*
|
|
4
|
+
* Handles copy and cut with both plain text and RTIF JSON clipboard formats.
|
|
5
|
+
* Paste can be delegated to the content pipeline (skipPaste mode) or handled
|
|
6
|
+
* directly for plain text fallback.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/spec/platform-requirements.md section 4 (Clipboard)
|
|
9
|
+
*/
|
|
10
|
+
import { resolve, blockTextLength } from '@rtif-sdk/core';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// RTIF clipboard MIME type
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/** Custom MIME type for RTIF JSON clipboard data. */
|
|
15
|
+
const RTIF_MIME = 'application/x-rtif+json';
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Pure helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* Extract plain text from a document range defined by absolute offsets.
|
|
21
|
+
*
|
|
22
|
+
* Walks blocks and spans within `[start, end)`, inserting `'\n'` at
|
|
23
|
+
* virtual block boundaries. This is a pure function with no side effects.
|
|
24
|
+
*
|
|
25
|
+
* @param doc - The RTIF document
|
|
26
|
+
* @param start - Start offset (inclusive)
|
|
27
|
+
* @param end - End offset (exclusive)
|
|
28
|
+
* @returns The plain text string within the range
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const doc = {
|
|
33
|
+
* version: 1,
|
|
34
|
+
* blocks: [
|
|
35
|
+
* { id: 'b1', type: 'text', spans: [{ text: 'hello' }] },
|
|
36
|
+
* { id: 'b2', type: 'text', spans: [{ text: 'world' }] },
|
|
37
|
+
* ],
|
|
38
|
+
* };
|
|
39
|
+
* extractPlainText(doc, 3, 8); // => 'lo\nwo'
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function extractPlainText(doc, start, end) {
|
|
43
|
+
if (start >= end)
|
|
44
|
+
return '';
|
|
45
|
+
const startRes = resolve(doc, start);
|
|
46
|
+
const endRes = resolve(doc, end);
|
|
47
|
+
const parts = [];
|
|
48
|
+
for (let bi = startRes.blockIndex; bi <= endRes.blockIndex; bi++) {
|
|
49
|
+
const block = doc.blocks[bi];
|
|
50
|
+
if (!block)
|
|
51
|
+
continue;
|
|
52
|
+
const blockLen = blockTextLength(block);
|
|
53
|
+
// Determine the local character range within this block
|
|
54
|
+
const localStart = bi === startRes.blockIndex ? startRes.localOffset : 0;
|
|
55
|
+
const localEnd = bi === endRes.blockIndex ? endRes.localOffset : blockLen;
|
|
56
|
+
// Walk spans in this block and extract the relevant slice
|
|
57
|
+
let charsSoFar = 0;
|
|
58
|
+
for (const span of block.spans) {
|
|
59
|
+
const spanStart = charsSoFar;
|
|
60
|
+
const spanEnd = charsSoFar + span.text.length;
|
|
61
|
+
// Skip spans entirely before our range
|
|
62
|
+
if (spanEnd <= localStart) {
|
|
63
|
+
charsSoFar = spanEnd;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Stop if span starts at or past our range end
|
|
67
|
+
if (spanStart >= localEnd) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
// Compute the slice within this span
|
|
71
|
+
const sliceStart = Math.max(localStart - spanStart, 0);
|
|
72
|
+
const sliceEnd = Math.min(localEnd - spanStart, span.text.length);
|
|
73
|
+
parts.push(span.text.slice(sliceStart, sliceEnd));
|
|
74
|
+
charsSoFar = spanEnd;
|
|
75
|
+
}
|
|
76
|
+
// Add a newline between blocks (not after the last one)
|
|
77
|
+
if (bi < endRes.blockIndex) {
|
|
78
|
+
parts.push('\n');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return parts.join('');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extract an RTIF block slice from a document range for clipboard serialization.
|
|
85
|
+
*
|
|
86
|
+
* Extracts blocks and spans within the absolute offset range `[start, end)`,
|
|
87
|
+
* preserving span marks and block attributes. The resulting blocks form a
|
|
88
|
+
* self-contained document fragment suitable for clipboard storage.
|
|
89
|
+
*
|
|
90
|
+
* @param doc - The RTIF document
|
|
91
|
+
* @param start - Start offset (inclusive)
|
|
92
|
+
* @param end - End offset (exclusive)
|
|
93
|
+
* @returns Array of extracted blocks with sliced spans
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const doc = {
|
|
98
|
+
* version: 1,
|
|
99
|
+
* blocks: [
|
|
100
|
+
* {
|
|
101
|
+
* id: 'b1', type: 'text',
|
|
102
|
+
* spans: [
|
|
103
|
+
* { text: 'hello ', marks: { bold: true } },
|
|
104
|
+
* { text: 'world' },
|
|
105
|
+
* ],
|
|
106
|
+
* },
|
|
107
|
+
* ],
|
|
108
|
+
* };
|
|
109
|
+
* const blocks = extractRtifSlice(doc, 0, 5);
|
|
110
|
+
* // => [{ id: 'b1', type: 'text', spans: [{ text: 'hello', marks: { bold: true } }] }]
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function extractRtifSlice(doc, start, end) {
|
|
114
|
+
if (start >= end)
|
|
115
|
+
return [];
|
|
116
|
+
const startRes = resolve(doc, start);
|
|
117
|
+
const endRes = resolve(doc, end);
|
|
118
|
+
const result = [];
|
|
119
|
+
for (let bi = startRes.blockIndex; bi <= endRes.blockIndex; bi++) {
|
|
120
|
+
const block = doc.blocks[bi];
|
|
121
|
+
if (!block)
|
|
122
|
+
continue;
|
|
123
|
+
const blockLen = blockTextLength(block);
|
|
124
|
+
// Determine the local character range within this block
|
|
125
|
+
const localStart = bi === startRes.blockIndex ? startRes.localOffset : 0;
|
|
126
|
+
const localEnd = bi === endRes.blockIndex ? endRes.localOffset : blockLen;
|
|
127
|
+
// Extract spans within the local range
|
|
128
|
+
const extractedSpans = extractSpanSlice(block.spans, localStart, localEnd);
|
|
129
|
+
// Build the extracted block
|
|
130
|
+
const extractedBlock = {
|
|
131
|
+
id: block.id,
|
|
132
|
+
type: block.type,
|
|
133
|
+
spans: extractedSpans.length > 0 ? extractedSpans : [{ text: '' }],
|
|
134
|
+
};
|
|
135
|
+
if (block.attrs && Object.keys(block.attrs).length > 0) {
|
|
136
|
+
extractedBlock.attrs = { ...block.attrs };
|
|
137
|
+
}
|
|
138
|
+
result.push(extractedBlock);
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Extract a slice of spans within a local offset range.
|
|
144
|
+
*
|
|
145
|
+
* @param spans - The source spans
|
|
146
|
+
* @param localStart - Start offset within the block
|
|
147
|
+
* @param localEnd - End offset within the block
|
|
148
|
+
* @returns Array of sliced spans preserving marks
|
|
149
|
+
*/
|
|
150
|
+
function extractSpanSlice(spans, localStart, localEnd) {
|
|
151
|
+
const result = [];
|
|
152
|
+
let charsSoFar = 0;
|
|
153
|
+
for (const span of spans) {
|
|
154
|
+
const spanStart = charsSoFar;
|
|
155
|
+
const spanEnd = charsSoFar + span.text.length;
|
|
156
|
+
// Skip spans entirely before our range
|
|
157
|
+
if (spanEnd <= localStart) {
|
|
158
|
+
charsSoFar = spanEnd;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Stop if span starts at or past our range end
|
|
162
|
+
if (spanStart >= localEnd) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
// Compute the slice within this span
|
|
166
|
+
const sliceStart = Math.max(localStart - spanStart, 0);
|
|
167
|
+
const sliceEnd = Math.min(localEnd - spanStart, span.text.length);
|
|
168
|
+
const slicedText = span.text.slice(sliceStart, sliceEnd);
|
|
169
|
+
if (slicedText.length > 0) {
|
|
170
|
+
const slicedSpan = { text: slicedText };
|
|
171
|
+
if (span.marks && Object.keys(span.marks).length > 0) {
|
|
172
|
+
slicedSpan.marks = { ...span.marks };
|
|
173
|
+
}
|
|
174
|
+
result.push(slicedSpan);
|
|
175
|
+
}
|
|
176
|
+
charsSoFar = spanEnd;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Generate the RTIF operations needed to delete a selection range.
|
|
182
|
+
*
|
|
183
|
+
* For same-block selections, produces a single `delete_text`. For cross-block
|
|
184
|
+
* selections, composes `delete_text` and `merge_block` operations to remove
|
|
185
|
+
* all content within the range and merge the remaining fragments.
|
|
186
|
+
*
|
|
187
|
+
* The generated operations are designed to be dispatched as a single batch.
|
|
188
|
+
* After each operation, the merge point stays at the same absolute offset
|
|
189
|
+
* (`start`), which makes the sequencing correct.
|
|
190
|
+
*
|
|
191
|
+
* @param doc - The current RTIF document
|
|
192
|
+
* @param selection - The selection to delete
|
|
193
|
+
* @returns An array of operations (may be empty if selection is collapsed)
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* // Single-block deletion
|
|
198
|
+
* const ops = deleteSelectionOps(doc, {
|
|
199
|
+
* anchor: { offset: 2 },
|
|
200
|
+
* focus: { offset: 5 },
|
|
201
|
+
* });
|
|
202
|
+
* // => [{ type: 'delete_text', offset: 2, count: 3 }]
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function deleteSelectionOps(doc, selection) {
|
|
206
|
+
const start = Math.min(selection.anchor.offset, selection.focus.offset);
|
|
207
|
+
const end = Math.max(selection.anchor.offset, selection.focus.offset);
|
|
208
|
+
if (start === end)
|
|
209
|
+
return [];
|
|
210
|
+
const startRes = resolve(doc, start);
|
|
211
|
+
const endRes = resolve(doc, end);
|
|
212
|
+
// Same block: single delete_text
|
|
213
|
+
if (startRes.blockIndex === endRes.blockIndex) {
|
|
214
|
+
return [{ type: 'delete_text', offset: start, count: end - start }];
|
|
215
|
+
}
|
|
216
|
+
// Cross-block deletion:
|
|
217
|
+
//
|
|
218
|
+
// Strategy: after each operation, the "merge point" is always at absolute
|
|
219
|
+
// offset `start`. This works because:
|
|
220
|
+
// - Deleting text at end of first block removes chars after `start`
|
|
221
|
+
// - Merging the next block appends its content at `start`
|
|
222
|
+
// - Deleting that block's content removes chars starting at `start`
|
|
223
|
+
//
|
|
224
|
+
// So every op targets offset `start`.
|
|
225
|
+
const ops = [];
|
|
226
|
+
const firstBlock = doc.blocks[startRes.blockIndex];
|
|
227
|
+
if (!firstBlock)
|
|
228
|
+
return [];
|
|
229
|
+
const firstBlockLen = blockTextLength(firstBlock);
|
|
230
|
+
// 1. Delete from startLocal to end of first block
|
|
231
|
+
const deleteInFirst = firstBlockLen - startRes.localOffset;
|
|
232
|
+
if (deleteInFirst > 0) {
|
|
233
|
+
ops.push({ type: 'delete_text', offset: start, count: deleteInFirst });
|
|
234
|
+
}
|
|
235
|
+
// 2. For each subsequent block through the last: merge, then delete content
|
|
236
|
+
for (let i = startRes.blockIndex + 1; i <= endRes.blockIndex; i++) {
|
|
237
|
+
const block = doc.blocks[i];
|
|
238
|
+
if (!block)
|
|
239
|
+
continue;
|
|
240
|
+
const isLast = i === endRes.blockIndex;
|
|
241
|
+
const bLen = blockTextLength(block);
|
|
242
|
+
// Merge this block into the accumulated first block
|
|
243
|
+
ops.push({ type: 'merge_block', blockId: block.id });
|
|
244
|
+
if (isLast) {
|
|
245
|
+
// Delete from start of this (last) block up to endLocal
|
|
246
|
+
if (endRes.localOffset > 0) {
|
|
247
|
+
ops.push({ type: 'delete_text', offset: start, count: endRes.localOffset });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Delete all content from this intermediate block
|
|
252
|
+
if (bLen > 0) {
|
|
253
|
+
ops.push({ type: 'delete_text', offset: start, count: bLen });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return ops;
|
|
258
|
+
}
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// ClipboardHandler class
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
/**
|
|
263
|
+
* Handles copy, cut, and paste events on a contenteditable root element.
|
|
264
|
+
*
|
|
265
|
+
* Copy and cut write both `text/plain` and `application/x-rtif+json` to the
|
|
266
|
+
* clipboard. Paste handling can be delegated to the content pipeline by
|
|
267
|
+
* setting `skipPaste: true` in the deps.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const handler = new ClipboardHandler(root, deps);
|
|
272
|
+
* handler.attach(); // start listening
|
|
273
|
+
* // ... later ...
|
|
274
|
+
* handler.detach(); // stop listening
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export class ClipboardHandler {
|
|
278
|
+
_root;
|
|
279
|
+
_deps;
|
|
280
|
+
_copyHandler = null;
|
|
281
|
+
_cutHandler = null;
|
|
282
|
+
_pasteHandler = null;
|
|
283
|
+
constructor(root, deps) {
|
|
284
|
+
this._root = root;
|
|
285
|
+
this._deps = deps;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Attach copy, cut, and (optionally) paste event listeners to the root element.
|
|
289
|
+
*
|
|
290
|
+
* When `skipPaste` is true in the deps, the paste listener is not attached.
|
|
291
|
+
* The content pipeline handles paste instead.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* handler.attach();
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
attach() {
|
|
299
|
+
this._copyHandler = (e) => this._onCopy(e);
|
|
300
|
+
this._cutHandler = (e) => this._onCut(e);
|
|
301
|
+
this._root.addEventListener('copy', this._copyHandler);
|
|
302
|
+
this._root.addEventListener('cut', this._cutHandler);
|
|
303
|
+
// Only attach paste if not delegated to content pipeline
|
|
304
|
+
if (!this._deps.skipPaste) {
|
|
305
|
+
this._pasteHandler = (e) => this._onPaste(e);
|
|
306
|
+
this._root.addEventListener('paste', this._pasteHandler);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Remove all clipboard event listeners from the root element.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* handler.detach();
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
detach() {
|
|
318
|
+
if (this._copyHandler) {
|
|
319
|
+
this._root.removeEventListener('copy', this._copyHandler);
|
|
320
|
+
this._copyHandler = null;
|
|
321
|
+
}
|
|
322
|
+
if (this._cutHandler) {
|
|
323
|
+
this._root.removeEventListener('cut', this._cutHandler);
|
|
324
|
+
this._cutHandler = null;
|
|
325
|
+
}
|
|
326
|
+
if (this._pasteHandler) {
|
|
327
|
+
this._root.removeEventListener('paste', this._pasteHandler);
|
|
328
|
+
this._pasteHandler = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// -----------------------------------------------------------------------
|
|
332
|
+
// Event handlers
|
|
333
|
+
// -----------------------------------------------------------------------
|
|
334
|
+
_onCopy(e) {
|
|
335
|
+
const sel = this._deps.getSelection();
|
|
336
|
+
const start = Math.min(sel.anchor.offset, sel.focus.offset);
|
|
337
|
+
const end = Math.max(sel.anchor.offset, sel.focus.offset);
|
|
338
|
+
// Nothing to copy if selection is collapsed
|
|
339
|
+
if (start === end)
|
|
340
|
+
return;
|
|
341
|
+
const doc = this._deps.getDoc();
|
|
342
|
+
const text = extractPlainText(doc, start, end);
|
|
343
|
+
if (e.clipboardData) {
|
|
344
|
+
e.clipboardData.setData('text/plain', text);
|
|
345
|
+
// Write RTIF JSON for rich paste within RTIF editors
|
|
346
|
+
const blocks = extractRtifSlice(doc, start, end);
|
|
347
|
+
const rtifJson = JSON.stringify({ version: 1, blocks });
|
|
348
|
+
e.clipboardData.setData(RTIF_MIME, rtifJson);
|
|
349
|
+
}
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
}
|
|
352
|
+
_onCut(e) {
|
|
353
|
+
// Read-only: allow browser default (which is nothing, since
|
|
354
|
+
// contenteditable won't mutate), but prevent any mutation attempt.
|
|
355
|
+
if (this._deps.isReadOnly()) {
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const sel = this._deps.getSelection();
|
|
360
|
+
const start = Math.min(sel.anchor.offset, sel.focus.offset);
|
|
361
|
+
const end = Math.max(sel.anchor.offset, sel.focus.offset);
|
|
362
|
+
// Nothing to cut if selection is collapsed
|
|
363
|
+
if (start === end) {
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
// Write to clipboard
|
|
368
|
+
const doc = this._deps.getDoc();
|
|
369
|
+
const text = extractPlainText(doc, start, end);
|
|
370
|
+
if (e.clipboardData) {
|
|
371
|
+
e.clipboardData.setData('text/plain', text);
|
|
372
|
+
// Write RTIF JSON for rich paste within RTIF editors
|
|
373
|
+
const blocks = extractRtifSlice(doc, start, end);
|
|
374
|
+
const rtifJson = JSON.stringify({ version: 1, blocks });
|
|
375
|
+
e.clipboardData.setData(RTIF_MIME, rtifJson);
|
|
376
|
+
}
|
|
377
|
+
e.preventDefault();
|
|
378
|
+
// Delete the selection
|
|
379
|
+
const ops = deleteSelectionOps(doc, sel);
|
|
380
|
+
if (ops.length > 0) {
|
|
381
|
+
this._deps.dispatch(ops);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
_onPaste(e) {
|
|
385
|
+
e.preventDefault();
|
|
386
|
+
if (this._deps.isReadOnly())
|
|
387
|
+
return;
|
|
388
|
+
if (this._deps.isComposing())
|
|
389
|
+
return;
|
|
390
|
+
const pastedText = e.clipboardData?.getData('text/plain') ?? '';
|
|
391
|
+
if (pastedText === '')
|
|
392
|
+
return;
|
|
393
|
+
const doc = this._deps.getDoc();
|
|
394
|
+
const sel = this._deps.getSelection();
|
|
395
|
+
const ops = [];
|
|
396
|
+
// If selection is non-collapsed, delete it first
|
|
397
|
+
const start = Math.min(sel.anchor.offset, sel.focus.offset);
|
|
398
|
+
const end = Math.max(sel.anchor.offset, sel.focus.offset);
|
|
399
|
+
if (start !== end) {
|
|
400
|
+
ops.push(...deleteSelectionOps(doc, sel));
|
|
401
|
+
}
|
|
402
|
+
// Split pasted text on newlines
|
|
403
|
+
const lines = pastedText.split('\n');
|
|
404
|
+
let insertOffset = start;
|
|
405
|
+
for (let i = 0; i < lines.length; i++) {
|
|
406
|
+
const line = lines[i];
|
|
407
|
+
if (i > 0) {
|
|
408
|
+
// Insert a block split before each subsequent line
|
|
409
|
+
ops.push({
|
|
410
|
+
type: 'split_block',
|
|
411
|
+
offset: insertOffset,
|
|
412
|
+
newBlockId: this._deps.generateBlockId(),
|
|
413
|
+
});
|
|
414
|
+
// After split_block, the offset stays the same (now at start of new block),
|
|
415
|
+
// but the absolute offset increases by 1 for the virtual \n
|
|
416
|
+
insertOffset += 1;
|
|
417
|
+
}
|
|
418
|
+
if (line.length > 0) {
|
|
419
|
+
ops.push({
|
|
420
|
+
type: 'insert_text',
|
|
421
|
+
offset: insertOffset,
|
|
422
|
+
text: line,
|
|
423
|
+
});
|
|
424
|
+
insertOffset += line.length;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (ops.length > 0) {
|
|
428
|
+
this._deps.dispatch(ops);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
//# sourceMappingURL=clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command bus — bridges between UI controls (toolbars, shortcuts) and the
|
|
3
|
+
* engine's command system.
|
|
4
|
+
*
|
|
5
|
+
* The command bus wraps `engine.exec()`, `engine.canExecute()`, and
|
|
6
|
+
* `engine.isActive()` and notifies subscribers (toolbars) whenever command
|
|
7
|
+
* state might have changed.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { IEditorEngine } from '@rtif-sdk/engine';
|
|
12
|
+
/**
|
|
13
|
+
* Command bus interface for dispatching editor commands and querying state.
|
|
14
|
+
*
|
|
15
|
+
* UI controls (toolbars, menus) use this interface to:
|
|
16
|
+
* - Execute commands (`execute`)
|
|
17
|
+
* - Query whether a command is available (`canExecute`)
|
|
18
|
+
* - Query whether a command's effect is active (`isActive`)
|
|
19
|
+
* - Subscribe to state changes so the UI updates live (`subscribe`)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const bus = createCommandBus(engine);
|
|
24
|
+
*
|
|
25
|
+
* // Execute a command
|
|
26
|
+
* bus.execute('toggleMark:bold');
|
|
27
|
+
*
|
|
28
|
+
* // Query state for toolbar rendering
|
|
29
|
+
* const boldActive = bus.isActive('toggleMark:bold');
|
|
30
|
+
* const canUndo = bus.canExecute('undo');
|
|
31
|
+
*
|
|
32
|
+
* // Subscribe for live updates
|
|
33
|
+
* const unsub = bus.subscribe(() => updateToolbar(bus));
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface CommandBus {
|
|
37
|
+
/**
|
|
38
|
+
* Execute a command by name.
|
|
39
|
+
*
|
|
40
|
+
* @param name - The command name
|
|
41
|
+
* @param payload - Optional payload for the command handler
|
|
42
|
+
* @returns `true` if the command executed successfully, `false` on error
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* bus.execute('toggleMark:bold');
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
execute(name: string, payload?: unknown): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a command can execute in the current state.
|
|
52
|
+
*
|
|
53
|
+
* @param name - The command name
|
|
54
|
+
* @param payload - Optional payload
|
|
55
|
+
* @returns Whether the command can execute
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* if (bus.canExecute('undo')) { ... }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
canExecute(name: string, payload?: unknown): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Check whether a command's effect is currently active.
|
|
65
|
+
*
|
|
66
|
+
* @param name - The command name
|
|
67
|
+
* @param payload - Optional payload
|
|
68
|
+
* @returns Whether the command's effect is active
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const boldActive = bus.isActive('toggleMark:bold');
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
isActive(name: string, payload?: unknown): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to state changes. The handler is called after every engine
|
|
78
|
+
* state change so toolbars can re-render.
|
|
79
|
+
*
|
|
80
|
+
* @param handler - Callback invoked with the command bus after each change
|
|
81
|
+
* @returns Unsubscribe function
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const unsub = bus.subscribe((b) => updateToolbar(b));
|
|
86
|
+
* // later:
|
|
87
|
+
* unsub();
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
subscribe(handler: (bus: CommandBus) => void): () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Clean up subscriptions. Called during editor teardown.
|
|
93
|
+
*/
|
|
94
|
+
destroy(): void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a command bus backed by the given engine.
|
|
98
|
+
*
|
|
99
|
+
* The bus subscribes to `engine.onChange()` to notify UI subscribers whenever
|
|
100
|
+
* command state might have changed (e.g., undo availability, bold active state).
|
|
101
|
+
*
|
|
102
|
+
* @param engine - The RTIF engine instance
|
|
103
|
+
* @returns A CommandBus instance
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const engine = createEngine(doc);
|
|
108
|
+
* const bus = createCommandBus(engine);
|
|
109
|
+
* bus.execute('toggleMark:bold');
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function createCommandBus(engine: IEditorEngine): CommandBus;
|
|
113
|
+
//# sourceMappingURL=command-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-bus.d.ts","sourceRoot":"","sources":["../src/command-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMtD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAElD;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAErD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEnD;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAE1D;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CA8ClE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command bus — bridges between UI controls (toolbars, shortcuts) and the
|
|
3
|
+
* engine's command system.
|
|
4
|
+
*
|
|
5
|
+
* The command bus wraps `engine.exec()`, `engine.canExecute()`, and
|
|
6
|
+
* `engine.isActive()` and notifies subscribers (toolbars) whenever command
|
|
7
|
+
* state might have changed.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Factory
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Create a command bus backed by the given engine.
|
|
16
|
+
*
|
|
17
|
+
* The bus subscribes to `engine.onChange()` to notify UI subscribers whenever
|
|
18
|
+
* command state might have changed (e.g., undo availability, bold active state).
|
|
19
|
+
*
|
|
20
|
+
* @param engine - The RTIF engine instance
|
|
21
|
+
* @returns A CommandBus instance
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const engine = createEngine(doc);
|
|
26
|
+
* const bus = createCommandBus(engine);
|
|
27
|
+
* bus.execute('toggleMark:bold');
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createCommandBus(engine) {
|
|
31
|
+
const subscribers = new Set();
|
|
32
|
+
const notifySubscribers = () => {
|
|
33
|
+
for (const handler of subscribers) {
|
|
34
|
+
handler(bus);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const bus = {
|
|
38
|
+
execute(name, payload) {
|
|
39
|
+
try {
|
|
40
|
+
engine.exec(name, payload);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
canExecute(name, payload) {
|
|
48
|
+
return engine.canExecute(name, payload);
|
|
49
|
+
},
|
|
50
|
+
isActive(name, payload) {
|
|
51
|
+
return engine.isActive(name, payload);
|
|
52
|
+
},
|
|
53
|
+
subscribe(handler) {
|
|
54
|
+
subscribers.add(handler);
|
|
55
|
+
return () => {
|
|
56
|
+
subscribers.delete(handler);
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
destroy() {
|
|
60
|
+
unsubChange();
|
|
61
|
+
unsubSelection();
|
|
62
|
+
subscribers.clear();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// Subscribe to engine changes and notify all bus subscribers
|
|
66
|
+
const unsubChange = engine.onChange(notifySubscribers);
|
|
67
|
+
const unsubSelection = engine.onSelectionChange(notifySubscribers);
|
|
68
|
+
return bus;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=command-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-bus.js","sourceRoot":"","sources":["../src/command-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiGH,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAqB;IACpD,MAAM,WAAW,GAAG,IAAI,GAAG,EAA6B,CAAC;IAEzD,MAAM,iBAAiB,GAAG,GAAS,EAAE;QACnC,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,GAAG,GAAe;QACtB,OAAO,CAAC,IAAY,EAAE,OAAiB;YACrC,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAC3B,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,UAAU,CAAC,IAAY,EAAE,OAAiB;YACxC,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;QAED,QAAQ,CAAC,IAAY,EAAE,OAAiB;YACtC,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;QAED,SAAS,CAAC,OAAkC;YAC1C,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACzB,OAAO,GAAG,EAAE;gBACV,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC,CAAC;QACJ,CAAC;QAED,OAAO;YACL,WAAW,EAAE,CAAC;YACd,cAAc,EAAE,CAAC;YACjB,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;IAEF,6DAA6D;IAC7D,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,MAAM,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAEnE,OAAO,GAAG,CAAC;AACb,CAAC"}
|