@inshapardaz/likhari-react 0.1.18 → 0.1.19

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.
@@ -0,0 +1,481 @@
1
+ import { $findMatchingParent } from '@lexical/utils';
2
+ import { $createParagraphNode, $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection, } from 'lexical';
3
+ import { $createLayoutContainerNode, $createLayoutItemNode, $isLayoutContainerNode, $isLayoutItemNode } from './LayoutNode.js';
4
+ import { $createPoetryBlockNode, $isPoetryBlockNode, POETRY_GUTTERS, POETRY_SPACINGS, POETRY_STAGGERS, } from './PoetryNode.js';
5
+ /**
6
+ * A poetry block is a *section* of one or more couplets sharing one layout
7
+ * (single-column stacks each couplet's two misras directly as paragraph
8
+ * children, two deep per couplet; two-column gives each couplet its own
9
+ * LayoutContainerNode row). This walks that structure and returns each
10
+ * couplet as a pair, in document order — the one place that knows how to
11
+ * read either shape, so nothing else needs to.
12
+ */
13
+ export function $getCouplets(node) {
14
+ if (node.getLayout() !== 'two-column') {
15
+ const paragraphs = node.getChildren().filter($isParagraphNode);
16
+ const couplets = [];
17
+ for (let i = 0; i + 1 < paragraphs.length; i += 2)
18
+ couplets.push([paragraphs[i], paragraphs[i + 1]]);
19
+ return couplets;
20
+ }
21
+ const containers = node.getChildren().filter($isLayoutContainerNode);
22
+ const couplets = [];
23
+ for (const container of containers) {
24
+ const items = container.getChildren().filter($isLayoutItemNode);
25
+ if (items.length === 1) {
26
+ const [a, b] = items[0].getChildren();
27
+ if ($isParagraphNode(a) && $isParagraphNode(b))
28
+ couplets.push([a, b]);
29
+ continue;
30
+ }
31
+ const a = items[0]?.getFirstChild();
32
+ const b = items[1]?.getFirstChild();
33
+ if ($isParagraphNode(a) && $isParagraphNode(b))
34
+ couplets.push([a, b]);
35
+ }
36
+ return couplets;
37
+ }
38
+ /** Every misra in the block, flattened across all its couplets — for
39
+ * operations (like $setPoetryLayout) that don't care about couplet
40
+ * boundaries, just the full set of lines. */
41
+ export function $getMisraParagraphs(node) {
42
+ return $getCouplets(node).flat();
43
+ }
44
+ /** The PoetryBlockNode the current selection is inside, or null. */
45
+ export function $getPoetryBlockFromSelection() {
46
+ const selection = $getSelection();
47
+ if (!$isRangeSelection(selection))
48
+ return null;
49
+ const match = $findMatchingParent(selection.anchor.getNode(), $isPoetryBlockNode);
50
+ return match ?? null;
51
+ }
52
+ /** Which couplet (and which of its two misras, 0 or 1) a node sits inside,
53
+ * or null if it's in neither — e.g. on the block's own boundary. */
54
+ function $findCoupletPosition(couplets, target) {
55
+ for (let c = 0; c < couplets.length; c++) {
56
+ for (const misraIndex of [0, 1]) {
57
+ const misra = couplets[c][misraIndex];
58
+ if (misra.getKey() === target.getKey() || misra.isParentOf(target))
59
+ return { coupletIndex: c, misraIndex };
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ function $appendCoupletTo(node) {
65
+ const misraA = $createParagraphNode();
66
+ const misraB = $createParagraphNode();
67
+ if (node.getLayout() !== 'two-column') {
68
+ node.append(misraA, misraB);
69
+ }
70
+ else {
71
+ const container = $createLayoutContainerNode('repeat(2, 1fr)');
72
+ container.append($createLayoutItemNode().append(misraA), $createLayoutItemNode().append(misraB));
73
+ node.append(container);
74
+ }
75
+ return [misraA, misraB];
76
+ }
77
+ /**
78
+ * Inserts a new, empty couplet immediately before or after an existing one —
79
+ * the table-row-insert equivalent for a poetry block ("add/remove couplets
80
+ * just like adding/removing table rows"). `existingFirstMisra` is that
81
+ * couplet's first misra, used to locate its wrapper (nothing, in single
82
+ * layout — just its two sibling paragraphs; a LayoutContainerNode row in
83
+ * two-column) so the new couplet can be spliced in at the right spot
84
+ * regardless of layout.
85
+ */
86
+ function $insertCoupletRelativeTo(existingFirstMisra, layout, position) {
87
+ const misraA = $createParagraphNode();
88
+ const misraB = $createParagraphNode();
89
+ if (layout !== 'two-column') {
90
+ if (position === 'before') {
91
+ existingFirstMisra.insertBefore(misraA);
92
+ misraA.insertAfter(misraB);
93
+ }
94
+ else {
95
+ const existingSecondMisra = existingFirstMisra.getNextSibling() ?? existingFirstMisra;
96
+ existingSecondMisra.insertAfter(misraA);
97
+ misraA.insertAfter(misraB);
98
+ }
99
+ }
100
+ else {
101
+ const container = $findMatchingParent(existingFirstMisra, $isLayoutContainerNode);
102
+ const newContainer = $createLayoutContainerNode('repeat(2, 1fr)');
103
+ newContainer.append($createLayoutItemNode().append(misraA), $createLayoutItemNode().append(misraB));
104
+ if (position === 'before')
105
+ container.insertBefore(newContainer);
106
+ else
107
+ container.insertAfter(newContainer);
108
+ }
109
+ return [misraA, misraB];
110
+ }
111
+ /**
112
+ * The explicit "Insert couplet above/below" menu actions — mirrors the
113
+ * table menu's "Insert row before/after". Targets the couplet the caret is
114
+ * currently in, falling back to the block's last couplet (so the toolbar's
115
+ * always-available insert button has somewhere sensible to act on even
116
+ * without a precise caret position).
117
+ */
118
+ export function $insertCoupletRelativeToSelection(position) {
119
+ const block = $getPoetryBlockFromSelection();
120
+ if (!block)
121
+ return false;
122
+ const selection = $getSelection();
123
+ const couplets = $getCouplets(block);
124
+ const anchorNode = $isRangeSelection(selection) ? selection.anchor.getNode() : null;
125
+ const found = anchorNode ? $findCoupletPosition(couplets, anchorNode) : null;
126
+ const index = found ? found.coupletIndex : couplets.length - 1;
127
+ const target = couplets[index];
128
+ if (!target)
129
+ return false;
130
+ const [newA] = $insertCoupletRelativeTo(target[0], block.getLayout(), position);
131
+ newA.selectStart();
132
+ return true;
133
+ }
134
+ /**
135
+ * Converts every couplet in the block between single-column (two stacked
136
+ * misras) and two-column (built on the columns primitive, §4.9) in place —
137
+ * pulls each couplet's two misra paragraphs out of whichever shape
138
+ * currently holds them and rebuilds the other shape around the same
139
+ * paragraph nodes, preserving couplet order, so content, selection and
140
+ * undo history all survive the conversion.
141
+ */
142
+ export function $setPoetryLayout(node, layout) {
143
+ if (node.getLayout() === layout)
144
+ return;
145
+ const couplets = $getCouplets(node);
146
+ for (const [a, b] of couplets) {
147
+ a.remove();
148
+ b.remove();
149
+ }
150
+ for (const leftover of node.getChildren())
151
+ leftover.remove();
152
+ if (layout !== 'two-column') {
153
+ for (const [a, b] of couplets)
154
+ node.append(a, b);
155
+ }
156
+ else {
157
+ for (const [a, b] of couplets) {
158
+ const container = $createLayoutContainerNode('repeat(2, 1fr)');
159
+ container.append($createLayoutItemNode().append(a), $createLayoutItemNode().append(b));
160
+ node.append(container);
161
+ }
162
+ }
163
+ node.setLayoutAttribute(layout);
164
+ }
165
+ /** A block at the very end of the document would otherwise leave no
166
+ * editable block to click or arrow down into — appends one empty paragraph
167
+ * if the block doesn't already have a next sibling. Idempotent: once a
168
+ * sibling exists (that paragraph, or anything else), this is a no-op, so
169
+ * it's safe to call from both the insert command and a node transform that
170
+ * keeps the invariant even after later edits (e.g. deleting the trailing
171
+ * paragraph, or JSON content loaded without one). */
172
+ export function $ensureTrailingParagraph(node) {
173
+ if (node.getNextSibling() === null)
174
+ node.insertAfter($createParagraphNode());
175
+ }
176
+ /**
177
+ * Inserts a poetry couplet at the caret. If the caret is already inside a
178
+ * poetry block, inserts a new couplet right after the one the caret is in
179
+ * (same as the "Insert couplet below" menu action — table-row-insert
180
+ * semantics, in its existing layout: "one poetry block can contain one or
181
+ * more couplets, in one or two column layout", so a block's layout is
182
+ * fixed once it has couplets, not chosen per couplet) and focuses its first
183
+ * misra. Otherwise creates a new block (in `layout`) with one
184
+ * couplet, after the selection's top-level element.
185
+ */
186
+ export function $insertPoetryCouplet(layout) {
187
+ const selection = $getSelection();
188
+ if (!$isRangeSelection(selection))
189
+ return false;
190
+ const existing = $getPoetryBlockFromSelection();
191
+ if (existing) {
192
+ return $insertCoupletRelativeToSelection('after');
193
+ }
194
+ const anchorTopLevel = selection.anchor.getNode().getTopLevelElementOrThrow();
195
+ const node = $createPoetryBlockNode(layout);
196
+ const [misraA] = $appendCoupletTo(node);
197
+ anchorTopLevel.insertAfter(node);
198
+ $ensureTrailingParagraph(node);
199
+ misraA.selectStart();
200
+ return true;
201
+ }
202
+ /** Removes the couplet the selection is inside. If it's the block's only
203
+ * couplet, removes the whole block (an empty poetry section serves no
204
+ * purpose) — the explicit, always-available counterpart to
205
+ * $deletePoetryOnBackspace's empty-couplet shortcut (mirrors the table
206
+ * menu's own "Delete table" action). */
207
+ export function $deletePoetryCouplet() {
208
+ const block = $getPoetryBlockFromSelection();
209
+ if (!block)
210
+ return false;
211
+ const selection = $getSelection();
212
+ const couplets = $getCouplets(block);
213
+ const anchorNode = $isRangeSelection(selection) ? selection.anchor.getNode() : null;
214
+ const position = anchorNode ? $findCoupletPosition(couplets, anchorNode) : null;
215
+ const target = position ? couplets[position.coupletIndex] : couplets[couplets.length - 1];
216
+ if (!target)
217
+ return false;
218
+ if (couplets.length <= 1) {
219
+ block.remove();
220
+ return true;
221
+ }
222
+ const [a, b] = target;
223
+ const container = $findMatchingParent(a, $isLayoutContainerNode);
224
+ if (container)
225
+ container.remove();
226
+ else {
227
+ a.remove();
228
+ b.remove();
229
+ }
230
+ return true;
231
+ }
232
+ /** Whether a point sits at the very start (or, with `atEnd`, very end) of a paragraph. */
233
+ function $isPointAt(paragraph, point, atEnd) {
234
+ if (point.key === paragraph.getKey())
235
+ return point.offset === (atEnd ? paragraph.getChildrenSize() : 0);
236
+ const edge = atEnd ? paragraph.getLastDescendant() : paragraph.getFirstDescendant();
237
+ if (point.getNode() !== edge)
238
+ return false;
239
+ return point.offset === (atEnd ? point.getNode().getTextContent().length : 0);
240
+ }
241
+ /**
242
+ * Backspace at the start of a misra never merges it into its neighbour: the
243
+ * block's lines are paired into couplets, so merging one line away would
244
+ * shift every couplet after it (and take the previous couplet's last line
245
+ * with it). Instead, Backspace at the start of an empty couplet removes that
246
+ * couplet, and at the start of any other line it moves the caret to the end
247
+ * of the previous line. Other caret positions (deleting within text) fall
248
+ * through to normal handling.
249
+ */
250
+ export function $deletePoetryOnBackspace() {
251
+ const selection = $getSelection();
252
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
253
+ return false;
254
+ const block = $getPoetryBlockFromSelection();
255
+ if (!block)
256
+ return false;
257
+ const couplets = $getCouplets(block);
258
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
259
+ if (!position)
260
+ return false;
261
+ const { coupletIndex, misraIndex } = position;
262
+ const [a, b] = couplets[coupletIndex];
263
+ const misra = couplets[coupletIndex][misraIndex];
264
+ if (!$isPointAt(misra, selection.anchor, false))
265
+ return false;
266
+ if (a.isEmpty() && b.isEmpty()) {
267
+ if (couplets.length <= 1) {
268
+ const previous = block.getPreviousSibling();
269
+ block.remove();
270
+ if ($isElementNode(previous))
271
+ previous.selectEnd();
272
+ return true;
273
+ }
274
+ const container = $findMatchingParent(a, $isLayoutContainerNode);
275
+ if (container)
276
+ container.remove();
277
+ else {
278
+ a.remove();
279
+ b.remove();
280
+ }
281
+ const remaining = $getCouplets(block);
282
+ const previousCouplet = remaining[coupletIndex - 1] ?? remaining[0];
283
+ previousCouplet?.[1].selectEnd();
284
+ return true;
285
+ }
286
+ const previousLine = misraIndex === 1 ? a : couplets[coupletIndex - 1]?.[1];
287
+ previousLine?.selectEnd();
288
+ return true;
289
+ }
290
+ /**
291
+ * Forward Delete at the end of a misra never merges the next line into it, for
292
+ * the same reason as $deletePoetryOnBackspace: the caret moves to the start of
293
+ * the next line instead. Other caret positions fall through to normal handling.
294
+ */
295
+ export function $deletePoetryForward() {
296
+ const selection = $getSelection();
297
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
298
+ return false;
299
+ const block = $getPoetryBlockFromSelection();
300
+ if (!block)
301
+ return false;
302
+ const couplets = $getCouplets(block);
303
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
304
+ if (!position)
305
+ return false;
306
+ const { coupletIndex, misraIndex } = position;
307
+ const misra = couplets[coupletIndex][misraIndex];
308
+ if (!$isPointAt(misra, selection.anchor, true))
309
+ return false;
310
+ const nextLine = misraIndex === 0 ? couplets[coupletIndex][1] : couplets[coupletIndex + 1]?.[0];
311
+ nextLine?.selectStart();
312
+ return true;
313
+ }
314
+ /**
315
+ * Enter inside a poetry block never grows a couplet past its fixed two
316
+ * misras. The block's *last* couplet being completely empty is always the
317
+ * exit trigger, regardless of which of its two (both-empty) misras the
318
+ * caret happens to be on — pressing Enter once on a non-empty last couplet
319
+ * appends a fresh empty one to continue into ("new couplet after last"),
320
+ * and pressing Enter again on that still-untouched couplet exits ("double
321
+ * enter to exit the block"): the same empty-item convention most editors
322
+ * use for lists. Outside that case: pressed in a couplet's first misra,
323
+ * Enter moves the caret to the second (already there; nothing inserted —
324
+ * this is what stops ParagraphNode's default insertNewAfter from growing
325
+ * the couplet, which would otherwise insert the new paragraph as a sibling
326
+ * of wherever the caret was, i.e. still inside the couplet); pressed in
327
+ * the last misra of a couplet that isn't the block's last, it moves to the
328
+ * next couplet's first misra.
329
+ */
330
+ export function $exitPoetryOnEnter() {
331
+ const selection = $getSelection();
332
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
333
+ return false;
334
+ const block = $getPoetryBlockFromSelection();
335
+ if (!block)
336
+ return false;
337
+ const couplets = $getCouplets(block);
338
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
339
+ if (!position)
340
+ return false;
341
+ const { coupletIndex, misraIndex } = position;
342
+ const [a, b] = couplets[coupletIndex];
343
+ const isLastCouplet = coupletIndex === couplets.length - 1;
344
+ const coupletEmpty = a.isEmpty() && b.isEmpty();
345
+ if (isLastCouplet && coupletEmpty) {
346
+ // Drop the couplet we ourselves auto-created on the previous Enter —
347
+ // unless it's the block's only couplet, in which case leave the
348
+ // section as the user's (empty) content rather than deleting it.
349
+ if (couplets.length > 1) {
350
+ const container = $findMatchingParent(a, $isLayoutContainerNode);
351
+ if (container)
352
+ container.remove();
353
+ else {
354
+ a.remove();
355
+ b.remove();
356
+ }
357
+ }
358
+ const existingNext = block.getNextSibling();
359
+ const paragraph = $isParagraphNode(existingNext) ? existingNext : $createParagraphNode();
360
+ if (paragraph !== existingNext)
361
+ block.insertAfter(paragraph);
362
+ paragraph.select();
363
+ return true;
364
+ }
365
+ if (misraIndex === 0) {
366
+ b.selectEnd();
367
+ return true;
368
+ }
369
+ if (!isLastCouplet) {
370
+ couplets[coupletIndex + 1][0].selectEnd();
371
+ return true;
372
+ }
373
+ const [newA] = $appendCoupletTo(block);
374
+ newA.selectStart();
375
+ return true;
376
+ }
377
+ /** Steps the caret's poetry block one notch looser (+1) or tighter (-1) on
378
+ * the couplet-spacing scale; clamps at either end. */
379
+ export function $adjustPoetrySpacing(step) {
380
+ const block = $getPoetryBlockFromSelection();
381
+ if (!block)
382
+ return false;
383
+ const index = POETRY_SPACINGS.indexOf(block.getSpacing());
384
+ const next = POETRY_SPACINGS[Math.min(POETRY_SPACINGS.length - 1, Math.max(0, index + step))];
385
+ block.setSpacing(next);
386
+ return true;
387
+ }
388
+ /** Steps the caret's two-column gutter one notch wider (+1) or narrower (-1);
389
+ * clamps at either end. */
390
+ export function $adjustPoetryGutter(step) {
391
+ const block = $getPoetryBlockFromSelection();
392
+ if (!block)
393
+ return false;
394
+ const index = POETRY_GUTTERS.indexOf(block.getGutter());
395
+ const next = POETRY_GUTTERS[Math.min(POETRY_GUTTERS.length - 1, Math.max(0, index + step))];
396
+ block.setGutter(next);
397
+ return true;
398
+ }
399
+ /** Steps the caret's staggered couplet width one notch narrower (-1) or wider
400
+ * (+1); clamps at either end. */
401
+ export function $adjustPoetryStagger(step) {
402
+ const block = $getPoetryBlockFromSelection();
403
+ if (!block)
404
+ return false;
405
+ const index = POETRY_STAGGERS.indexOf(block.getStagger());
406
+ const next = POETRY_STAGGERS[Math.min(POETRY_STAGGERS.length - 1, Math.max(0, index + step))];
407
+ block.setStagger(next);
408
+ return true;
409
+ }
410
+ /** A centered couplet is a two-column row holding both misras in one item
411
+ * (a single-item row), rather than one item per misra. */
412
+ function $isCoupletRowCentered(row) {
413
+ return row.getChildren().filter($isLayoutItemNode).length === 1;
414
+ }
415
+ function $findCoupletRow(couplet) {
416
+ return $findMatchingParent(couplet[0], $isLayoutContainerNode);
417
+ }
418
+ /** Whether the couplet the caret is in is centered, or null outside a
419
+ * two-column poetry block (centering only applies there). */
420
+ export function $getCoupletCenteredFromSelection() {
421
+ const block = $getPoetryBlockFromSelection();
422
+ if (!block || block.getLayout() !== 'two-column')
423
+ return null;
424
+ const selection = $getSelection();
425
+ if (!$isRangeSelection(selection))
426
+ return null;
427
+ const couplets = $getCouplets(block);
428
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
429
+ if (!position)
430
+ return null;
431
+ const row = $findCoupletRow(couplets[position.coupletIndex]);
432
+ return row ? $isCoupletRowCentered(row) : false;
433
+ }
434
+ /** Centers (or un-centers) the caret's couplet within a two-column block by
435
+ * rebuilding its row around the same two misra paragraphs. */
436
+ export function $setCoupletCentered(centered) {
437
+ const block = $getPoetryBlockFromSelection();
438
+ if (!block || block.getLayout() !== 'two-column')
439
+ return false;
440
+ const selection = $getSelection();
441
+ if (!$isRangeSelection(selection))
442
+ return false;
443
+ const couplets = $getCouplets(block);
444
+ const position = $findCoupletPosition(couplets, selection.anchor.getNode());
445
+ if (!position)
446
+ return false;
447
+ const couplet = couplets[position.coupletIndex];
448
+ const row = $findCoupletRow(couplet);
449
+ if (!row || $isCoupletRowCentered(row) === centered)
450
+ return true;
451
+ const [a, b] = couplet;
452
+ const newRow = $createLayoutContainerNode(centered ? '1fr' : 'repeat(2, 1fr)');
453
+ if (centered) {
454
+ newRow.append($createLayoutItemNode().append(a, b));
455
+ }
456
+ else {
457
+ newRow.append($createLayoutItemNode().append(a), $createLayoutItemNode().append(b));
458
+ }
459
+ row.insertAfter(newRow);
460
+ row.remove();
461
+ a.selectStart();
462
+ return true;
463
+ }
464
+ /** Removes the whole poetry block the caret is in, including every couplet. */
465
+ export function $deletePoetryBlock() {
466
+ const block = $getPoetryBlockFromSelection();
467
+ if (!block)
468
+ return false;
469
+ block.remove();
470
+ return true;
471
+ }
472
+ /** Single-column blocks pair their paragraphs into couplets, so a line left
473
+ * without a partner (from pasted or imported content) would be invisible to
474
+ * the couplet logic. Give it an empty partner instead, so every line belongs
475
+ * to a couplet and can be deleted like any other. */
476
+ export function $completeSingleCoupletBlock(node) {
477
+ if (node.getLayout() === 'two-column')
478
+ return;
479
+ if (node.getChildrenSize() % 2 === 1)
480
+ node.append($createParagraphNode());
481
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA8DA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA2avD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAg+BxJ"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AAqFA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA8gBvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAmkCxJ"}