react-word-editor 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATION.md ADDED
@@ -0,0 +1,106 @@
1
+ # Migrating flabs-frontend from TinyMCE
2
+
3
+ All ~20 TinyMCE screens go through one wrapper,
4
+ `src/components/TextEditor/textEditor.js`. Replace that file with the version
5
+ below and every screen moves over with no call-site changes.
6
+
7
+ ## 1. Install
8
+
9
+ ```bash
10
+ npm i react-word-editor
11
+ npm uninstall tinymce @tinymce/tinymce-react # once nothing imports them
12
+ ```
13
+
14
+ ## 2. Replace `src/components/TextEditor/textEditor.js`
15
+
16
+ ```jsx
17
+ import React from 'react';
18
+ import axios from 'axios';
19
+ import RichTextEditor from 'react-word-editor';
20
+ import 'react-word-editor/style.css';
21
+
22
+ const MAX_IMAGE_SIZE = 1024 * 1024;
23
+ const S3_BASE = 'https://radiology-flabs.s3.ap-south-1.amazonaws.com/';
24
+
25
+ const uploadToS3 = async (file) => {
26
+ const fileType = file.name?.split('.')?.pop() || file.type?.split('/')?.pop();
27
+ const { data } = await axios.get(`/s3UrlRadiology?fileType=${fileType}`);
28
+ await fetch(data?.url, { method: 'PUT', headers: { 'Content-Type': file.type }, body: file });
29
+ return `${S3_BASE}${data?.name}.${fileType}`;
30
+ };
31
+
32
+ const TextEditor = ({
33
+ height,
34
+ value,
35
+ setValue,
36
+ fnProps = [],
37
+ disabled,
38
+ dynamicData = {},
39
+ handleFocusRestore = () => {},
40
+ nodeIds = false,
41
+ onInit,
42
+ }) => (
43
+ <RichTextEditor
44
+ height={height || 500}
45
+ value={value}
46
+ setValue={setValue}
47
+ fnProps={fnProps}
48
+ disabled={disabled}
49
+ dynamicData={dynamicData}
50
+ handleFocusRestore={handleFocusRestore}
51
+ nodeIds={nodeIds}
52
+ onInit={onInit}
53
+ onImageUpload={uploadToS3}
54
+ maxImageSize={MAX_IMAGE_SIZE}
55
+ />
56
+ );
57
+
58
+ export default TextEditor;
59
+ ```
60
+
61
+ Then delete `BundledEditor.js`. `utils/constants.js` (`NODE_ID_*`) can go too
62
+ unless something else imports it.
63
+
64
+ ### Prop-by-prop compatibility
65
+
66
+ | Old TinyMCE wrapper prop | react-word-editor behaviour |
67
+ | --- | --- |
68
+ | `value` (HTML string) | Same. Legacy TinyMCE HTML loads as-is. |
69
+ | `setValue(html, ...fnProps)` | Same signature, same HTML output. |
70
+ | `fnProps` | Passed through as extra args. |
71
+ | `height` | Same (default 500 in the wrapper). |
72
+ | `disabled` | Read-only, toolbar greyed out. |
73
+ | `dynamicData` | `{{key}}` replaced on blur, formatting kept. |
74
+ | `handleFocusRestore` | Called on focus. |
75
+ | `nodeIds` | Same `data-node-id` attribute on the same block types. IDs are unique and are not copied when a paragraph is split. |
76
+ | `onInit(evt, editor)` | Called once. `editor.getContent()`, `setContent()`, `getBody()`, `focus()` work. The TipTap instance is `editor.tiptap`. |
77
+ | Image upload via `/s3UrlRadiology` | `onImageUpload(file) => url`. Unlike the old handler, the PUT is awaited and nothing falls back to a fake image. |
78
+
79
+ Only `SupportAgent`'s `EditableValues.jsx` uses `onInit`, and it only calls
80
+ `editor.getContent()`, so it works unchanged.
81
+
82
+ ## 3. Reports: drop `react-pdf-html-flabs-2` (optional, file by file)
83
+
84
+ `RichTextPdf` takes the same children / `style` shape as the `Html` component:
85
+
86
+ ```diff
87
+ - import Html from 'react-pdf-html-flabs-2';
88
+ + import Html from 'react-word-editor/pdf';
89
+ ```
90
+
91
+ ```jsx
92
+ <Html>{value}</Html>
93
+ <Html style={{ fontSize: 9 }} options={{ ignoreFontSizes: true }}>{interpretation}</Html>
94
+ ```
95
+
96
+ - `options.ignoreFontSizes` replaces the `removeFontSize(...)` helper.
97
+ - Pass registered fonts so content fonts are kept, e.g.
98
+ `options={{ fonts: ['Roboto'], fontMap: { Calibri: 'Roboto', Arial: 'Roboto' } }}`.
99
+ Any other family falls back to the surrounding font, because react-pdf throws
100
+ on unregistered fonts.
101
+ - Bold and italic text needs the matching weights registered for the font,
102
+ just as with the old renderer.
103
+
104
+ To skip HTML parsing at print time, save the editor's react-pdf tree next to
105
+ the HTML (`outputFormat="all"` gives `{ html, text, json, pdf }`). Then render
106
+ `<RichTextPdf value={savedTree} />`.
package/README.md CHANGED
@@ -1,3 +1,122 @@
1
- # Temporary Holding Version
1
+ # react-word-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Word-like rich text editor for React, built on [TipTap](https://tiptap.dev) / ProseMirror.
4
+
5
+ - **Word and Excel paste that keeps formatting:** tables (borders, shading,
6
+ column widths, merged cells), indentation and hanging indents, nested
7
+ bullet and numbered lists, alignment, line spacing, paragraph spacing,
8
+ fonts, sizes, colours, highlight, sub/superscript.
9
+ - **Reads HTML** (including legacy TinyMCE content), editor JSON or plain text.
10
+ - **Writes** HTML, plain text, JSON, or a **react-pdf tree** that renders
11
+ directly with `@react-pdf/renderer`, with no HTML-to-PDF step.
12
+
13
+ ```bash
14
+ npm i react-word-editor
15
+ # for PDF output
16
+ npm i @react-pdf/renderer
17
+ ```
18
+
19
+ ## Editor
20
+
21
+ ```jsx
22
+ import RichTextEditor from 'react-word-editor';
23
+ import 'react-word-editor/style.css';
24
+
25
+ <RichTextEditor value={html} onChange={setHtml} height={500} />;
26
+ ```
27
+
28
+ | Prop | Default | |
29
+ | --- | --- | --- |
30
+ | `value` | | HTML string, plain text, or editor JSON (`{ type: 'doc' }`). Controlled. |
31
+ | `onChange(value, ...fnProps)` | | Called on every change with the output in `outputFormat`. `setValue` is an alias. |
32
+ | `outputFormat` | `'html'` | `'html'` \| `'text'` \| `'json'` \| `'pdf'` \| `'all'` (`{ html, text, json, pdf }`). |
33
+ | `pdfOptions` | | Options for the `pdf` output (see below). |
34
+ | `fnProps` | `[]` | Extra arguments appended to `onChange`. |
35
+ | `height` | auto | Fixed height (number or CSS). Content scrolls inside. Otherwise it grows from `minHeight`. |
36
+ | `minHeight` | `200` | |
37
+ | `disabled` / `readOnly` | `false` | `readOnly` also hides the toolbar. |
38
+ | `placeholder` | | |
39
+ | `toolbar` | `true` | `false` to hide, or an array of item keys (see `DEFAULT_TOOLBAR`). `'|'` is a divider. |
40
+ | `fontFamilies` / `fontSizes` | | Toolbar choices. |
41
+ | `dynamicData` | | `{ key: value }`. `{{key}}` in the text is replaced on blur. |
42
+ | `nodeIds` | `false` | Stamp a unique `data-node-id` on every block. |
43
+ | `onImageUpload(file)` | base64 | Return a Promise of the image URL. Used for toolbar, paste and drop. |
44
+ | `maxImageSize` | | Bytes. |
45
+ | `onInit(evt, handle)` / `onReady(handle)` | | `handle.getContent({ format })`, `setContent`, `getText`, `getJSON`, `getPdfTree`, `getBody`, `focus`, `tiptap`. |
46
+ | `onFocus` / `onBlur` / `onError` | | |
47
+ | `pageView` | `false` | Shows the document as a white A4 page on a grey canvas, like Word. |
48
+ | `showWordCount` | `true` | |
49
+
50
+ Keyboard: the usual Ctrl/Cmd shortcuts, plus Tab / Shift+Tab to indent and
51
+ outdent. In a list they nest the item, and in a table they move between cells.
52
+
53
+ Word-style menus, also usable as commands on `handle.tiptap`:
54
+
55
+ | Toolbar key | Command | Values |
56
+ | --- | --- | --- |
57
+ | `textCase` | `setTextCase(mode)` | `sentence`, `lower`, `upper`, `capitalize`, `toggle`. Uses the selection, or the word under the cursor. |
58
+ | `paragraphSpacing` | `setParagraphSpacing(preset)` | `default`, `none`, `compact`, `tight`, `open`, `relaxed`, `double` (see `PARAGRAPH_SPACING`). |
59
+ | Table bar: Borders | `setTableBorders(mode)` | `top`, `bottom`, `left`, `right`, `insideH`, `insideV`, `outside`, `inside`, `all`, `none`. Uses the selected cells, or the current cell. |
60
+
61
+ Theme with CSS variables on `.fe-editor`: `--fe-primary`, `--fe-border`,
62
+ `--fe-content-font`, `--fe-content-size`, …
63
+
64
+ ## PDF (react-pdf)
65
+
66
+ ```jsx
67
+ import { Document, Page } from '@react-pdf/renderer';
68
+ import RichTextPdf from 'react-word-editor/pdf';
69
+
70
+ <Document>
71
+ <Page>
72
+ <RichTextPdf value={htmlOrJsonOrTree} options={{ fonts: ['Roboto'] }} />
73
+ </Page>
74
+ </Document>;
75
+ ```
76
+
77
+ `value` (or children) may be HTML, editor JSON or a stored pdf tree. Options:
78
+
79
+ | Option | Default | |
80
+ | --- | --- | --- |
81
+ | `fontSize` | inherited | Base size in pt. Headings scale from it. |
82
+ | `fonts` | `[]` | Registered font families that content may use. |
83
+ | `fontMap` | `{}` | Map CSS families to registered ones, e.g. `{ Calibri: 'Roboto' }`. |
84
+ | `ignoreFontSizes` | `false` | Ignore font sizes stored in the content. |
85
+ | `paragraphSpacing` | `4` | pt below paragraphs without explicit spacing. |
86
+ | `tableBorder` | `0.75pt solid #000000` | For cells without explicit borders. |
87
+ | `cellPadding` | `{ vertical: 2, horizontal: 4 }` | |
88
+ | `keepRowsTogether` | `true` | Stop table rows splitting across pages. |
89
+ | `styles` | `{}` | Per-node overrides: `root`, `paragraph`, `heading`, `heading1`…, `list`, `listItem`, `listMarker`, `table`, `tableRow`, `tableCell`, `tableHeader`, `image`, `link`, `text`, `blockquote`, `horizontalRule`. |
90
+
91
+ Lower level: `jsonToPdfTree(json, options)` returns plain serialisable data
92
+ (`{ type: 'view' | 'text' | 'link' | 'image', style, children }`), and
93
+ `renderPdfTree(tree)` turns it into react-pdf elements.
94
+
95
+ ## Converters (no React)
96
+
97
+ ```js
98
+ import { htmlToJSON, jsonToHTML, jsonToText, htmlToText, jsonToPdfTree, cleanPastedHtml } from 'react-word-editor/convert';
99
+ ```
100
+
101
+ `htmlToJSON` and `jsonToHTML` need a DOM (a browser, or jsdom / happy-dom on
102
+ the server).
103
+
104
+ ## Development
105
+
106
+ ```bash
107
+ npm install
108
+ npm run dev # playground at http://localhost:5199
109
+ npm test
110
+ npm run build
111
+ npm publish # runs tests + build first
112
+ ```
113
+
114
+ ## Known limits
115
+
116
+ - Images pasted straight out of Word arrive as local `file://` links that a
117
+ browser can't read, so they are dropped. Insert or drag the picture file in
118
+ instead.
119
+ - In PDFs, `rowspan` cells are drawn as a run of joined cells, and react-pdf
120
+ ignores negative (hanging) text indents.
121
+ - In PDFs, a highlighted word inside a paragraph with line spacing above 1.0
122
+ is shaded for the full line height (a react-pdf behaviour).
@@ -0,0 +1,38 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/editor/variables.js
2
+ var VARIABLE_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
3
+ var lookup = (data, key) => {
4
+ const value = data == null ? void 0 : data[key];
5
+ if (value === null || value === void 0 || value === "") return null;
6
+ return String(value);
7
+ };
8
+ var replaceVariables = (editor, data) => {
9
+ if (!editor || editor.isDestroyed || !data || !Object.keys(data).length) return false;
10
+ const { state } = editor;
11
+ const edits = [];
12
+ state.doc.descendants((node, pos) => {
13
+ if (!node.isText || !node.text.includes("{{")) return;
14
+ VARIABLE_RE.lastIndex = 0;
15
+ let match;
16
+ while (match = VARIABLE_RE.exec(node.text)) {
17
+ const value = lookup(data, match[1]);
18
+ if (value !== null) {
19
+ edits.push({ from: pos + match.index, to: pos + match.index + match[0].length, value });
20
+ }
21
+ }
22
+ });
23
+ if (!edits.length) return false;
24
+ const tr = state.tr;
25
+ edits.reverse().forEach(({ from, to, value }) => tr.insertText(value, from, to));
26
+ editor.view.dispatch(tr);
27
+ return true;
28
+ };
29
+ var replaceVariablesInString = (content, data) => String(content != null ? content : "").replace(VARIABLE_RE, (whole, key) => {
30
+ var _a;
31
+ return (_a = lookup(data, key)) != null ? _a : whole;
32
+ });
33
+
34
+
35
+
36
+
37
+ exports.replaceVariables = replaceVariables; exports.replaceVariablesInString = replaceVariablesInString;
38
+ //# sourceMappingURL=chunk-CTVYA4A3.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/harsh/Documents/Flabs/flabs-editor/dist/chunk-CTVYA4A3.cjs","../src/editor/variables.js"],"names":[],"mappings":"AAAA;ACAA,IAAM,YAAA,EAAc,0BAAA;AAEpB,IAAM,OAAA,EAAS,CAAC,IAAA,EAAM,GAAA,EAAA,GAAQ;AAC5B,EAAA,MAAM,MAAA,EAAQ,KAAA,GAAA,KAAA,EAAA,KAAA,EAAA,EAAA,IAAA,CAAO,GAAA,CAAA;AACrB,EAAA,GAAA,CAAI,MAAA,IAAU,KAAA,GAAQ,MAAA,IAAU,KAAA,EAAA,GAAa,MAAA,IAAU,EAAA,EAAI,OAAO,IAAA;AAClE,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA;AACrB,CAAA;AAIO,IAAM,iBAAA,EAAmB,CAAC,MAAA,EAAQ,IAAA,EAAA,GAAS;AAChD,EAAA,GAAA,CAAI,CAAC,OAAA,GAAU,MAAA,CAAO,YAAA,GAAe,CAAC,KAAA,GAAQ,CAAC,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAChF,EAAA,MAAM,EAAE,MAAM,EAAA,EAAI,MAAA;AAClB,EAAA,MAAM,MAAA,EAAQ,CAAC,CAAA;AACf,EAAA,KAAA,CAAM,GAAA,CAAI,WAAA,CAAY,CAAC,IAAA,EAAM,GAAA,EAAA,GAAQ;AACnC,IAAA,GAAA,CAAI,CAAC,IAAA,CAAK,OAAA,GAAU,CAAC,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG,MAAA;AAC/C,IAAA,WAAA,CAAY,UAAA,EAAY,CAAA;AACxB,IAAA,IAAI,KAAA;AACJ,IAAA,MAAA,CAAQ,MAAA,EAAQ,WAAA,CAAY,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAI;AAC5C,MAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,IAAA,EAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AACnC,MAAA,GAAA,CAAI,MAAA,IAAU,IAAA,EAAM;AAClB,QAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,CAAM,KAAA,EAAO,EAAA,EAAI,IAAA,EAAM,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,MACxF;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AACD,EAAA,GAAA,CAAI,CAAC,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAA;AAC1B,EAAA,MAAM,GAAA,EAAK,KAAA,CAAM,EAAA;AACjB,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,OAAA,CAAQ,CAAC,EAAE,IAAA,EAAM,EAAA,EAAI,MAAM,CAAA,EAAA,GAAM,EAAA,CAAG,UAAA,CAAW,KAAA,EAAO,IAAA,EAAM,EAAE,CAAC,CAAA;AAC/E,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA;AACvB,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,yBAAA,EAA2B,CAAC,OAAA,EAAS,IAAA,EAAA,GAChD,MAAA,CAAO,QAAA,GAAA,KAAA,EAAA,QAAA,EAAW,EAAE,CAAA,CAAE,OAAA,CAAQ,WAAA,EAAa,CAAC,KAAA,EAAO,GAAA,EAAA,GAAK;AAjC1D,EAAA,IAAA,EAAA;AAiC6D,EAAA,OAAA,CAAA,GAAA,EAAA,MAAA,CAAO,IAAA,EAAM,GAAG,CAAA,EAAA,GAAhB,KAAA,EAAA,GAAA,EAAqB,KAAA;AAAA,CAAK,CAAA;ADDvF;AACA;AACE;AACA;AACF,yGAAC","file":"/Users/harsh/Documents/Flabs/flabs-editor/dist/chunk-CTVYA4A3.cjs","sourcesContent":[null,"const VARIABLE_RE = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\nconst lookup = (data, key) => {\n const value = data?.[key];\n if (value === null || value === undefined || value === '') return null;\n return String(value);\n};\n\n// Replaces {{key}} placeholders in text with values from `data`, keeping the\n// text's formatting. Unknown keys are left untouched.\nexport const replaceVariables = (editor, data) => {\n if (!editor || editor.isDestroyed || !data || !Object.keys(data).length) return false;\n const { state } = editor;\n const edits = [];\n state.doc.descendants((node, pos) => {\n if (!node.isText || !node.text.includes('{{')) return;\n VARIABLE_RE.lastIndex = 0;\n let match;\n while ((match = VARIABLE_RE.exec(node.text))) {\n const value = lookup(data, match[1]);\n if (value !== null) {\n edits.push({ from: pos + match.index, to: pos + match.index + match[0].length, value });\n }\n }\n });\n if (!edits.length) return false;\n const tr = state.tr;\n edits.reverse().forEach(({ from, to, value }) => tr.insertText(value, from, to));\n editor.view.dispatch(tr);\n return true;\n};\n\nexport const replaceVariablesInString = (content, data) =>\n String(content ?? '').replace(VARIABLE_RE, (whole, key) => lookup(data, key) ?? whole);\n"]}
@@ -0,0 +1,38 @@
1
+ // src/editor/variables.js
2
+ var VARIABLE_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
3
+ var lookup = (data, key) => {
4
+ const value = data == null ? void 0 : data[key];
5
+ if (value === null || value === void 0 || value === "") return null;
6
+ return String(value);
7
+ };
8
+ var replaceVariables = (editor, data) => {
9
+ if (!editor || editor.isDestroyed || !data || !Object.keys(data).length) return false;
10
+ const { state } = editor;
11
+ const edits = [];
12
+ state.doc.descendants((node, pos) => {
13
+ if (!node.isText || !node.text.includes("{{")) return;
14
+ VARIABLE_RE.lastIndex = 0;
15
+ let match;
16
+ while (match = VARIABLE_RE.exec(node.text)) {
17
+ const value = lookup(data, match[1]);
18
+ if (value !== null) {
19
+ edits.push({ from: pos + match.index, to: pos + match.index + match[0].length, value });
20
+ }
21
+ }
22
+ });
23
+ if (!edits.length) return false;
24
+ const tr = state.tr;
25
+ edits.reverse().forEach(({ from, to, value }) => tr.insertText(value, from, to));
26
+ editor.view.dispatch(tr);
27
+ return true;
28
+ };
29
+ var replaceVariablesInString = (content, data) => String(content != null ? content : "").replace(VARIABLE_RE, (whole, key) => {
30
+ var _a;
31
+ return (_a = lookup(data, key)) != null ? _a : whole;
32
+ });
33
+
34
+ export {
35
+ replaceVariables,
36
+ replaceVariablesInString
37
+ };
38
+ //# sourceMappingURL=chunk-DCMBE2J7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/editor/variables.js"],"sourcesContent":["const VARIABLE_RE = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\nconst lookup = (data, key) => {\n const value = data?.[key];\n if (value === null || value === undefined || value === '') return null;\n return String(value);\n};\n\n// Replaces {{key}} placeholders in text with values from `data`, keeping the\n// text's formatting. Unknown keys are left untouched.\nexport const replaceVariables = (editor, data) => {\n if (!editor || editor.isDestroyed || !data || !Object.keys(data).length) return false;\n const { state } = editor;\n const edits = [];\n state.doc.descendants((node, pos) => {\n if (!node.isText || !node.text.includes('{{')) return;\n VARIABLE_RE.lastIndex = 0;\n let match;\n while ((match = VARIABLE_RE.exec(node.text))) {\n const value = lookup(data, match[1]);\n if (value !== null) {\n edits.push({ from: pos + match.index, to: pos + match.index + match[0].length, value });\n }\n }\n });\n if (!edits.length) return false;\n const tr = state.tr;\n edits.reverse().forEach(({ from, to, value }) => tr.insertText(value, from, to));\n editor.view.dispatch(tr);\n return true;\n};\n\nexport const replaceVariablesInString = (content, data) =>\n String(content ?? '').replace(VARIABLE_RE, (whole, key) => lookup(data, key) ?? whole);\n"],"mappings":";AAAA,IAAM,cAAc;AAEpB,IAAM,SAAS,CAAC,MAAM,QAAQ;AAC5B,QAAM,QAAQ,6BAAO;AACrB,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO;AAClE,SAAO,OAAO,KAAK;AACrB;AAIO,IAAM,mBAAmB,CAAC,QAAQ,SAAS;AAChD,MAAI,CAAC,UAAU,OAAO,eAAe,CAAC,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,OAAQ,QAAO;AAChF,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,QAAQ,CAAC;AACf,QAAM,IAAI,YAAY,CAAC,MAAM,QAAQ;AACnC,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,EAAG;AAC/C,gBAAY,YAAY;AACxB,QAAI;AACJ,WAAQ,QAAQ,YAAY,KAAK,KAAK,IAAI,GAAI;AAC5C,YAAM,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAC;AACnC,UAAI,UAAU,MAAM;AAClB,cAAM,KAAK,EAAE,MAAM,MAAM,MAAM,OAAO,IAAI,MAAM,MAAM,QAAQ,MAAM,CAAC,EAAE,QAAQ,MAAM,CAAC;AAAA,MACxF;AAAA,IACF;AAAA,EACF,CAAC;AACD,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAM,KAAK,MAAM;AACjB,QAAM,QAAQ,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,MAAM,MAAM,GAAG,WAAW,OAAO,MAAM,EAAE,CAAC;AAC/E,SAAO,KAAK,SAAS,EAAE;AACvB,SAAO;AACT;AAEO,IAAM,2BAA2B,CAAC,SAAS,SAChD,OAAO,4BAAW,EAAE,EAAE,QAAQ,aAAa,CAAC,OAAO,QAAK;AAjC1D;AAiC6D,sBAAO,MAAM,GAAG,MAAhB,YAAqB;AAAA,CAAK;","names":[]}