@dotvoid/textbit-plugins 0.0.0-stage → 0.1.1

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.
Files changed (91) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +194 -2
  3. package/dist/Audio/audioNode.cjs +1 -0
  4. package/dist/Audio/audioNode.d.ts +8 -0
  5. package/dist/Audio/audioNode.d.ts.map +1 -0
  6. package/dist/Audio/audioNode.js +15 -0
  7. package/dist/Audio/index.cjs +1 -0
  8. package/dist/Audio/index.d.ts +22 -0
  9. package/dist/Audio/index.d.ts.map +1 -0
  10. package/dist/Audio/index.js +63 -0
  11. package/dist/Blockquote/index.cjs +1 -0
  12. package/dist/Blockquote/index.d.ts +15 -0
  13. package/dist/Blockquote/index.d.ts.map +1 -0
  14. package/dist/Blockquote/index.js +56 -0
  15. package/dist/CodeBlock/index.cjs +1 -0
  16. package/dist/CodeBlock/index.d.ts +14 -0
  17. package/dist/CodeBlock/index.d.ts.map +1 -0
  18. package/dist/CodeBlock/index.js +65 -0
  19. package/dist/Image/imageNode.cjs +1 -0
  20. package/dist/Image/imageNode.d.ts +11 -0
  21. package/dist/Image/imageNode.d.ts.map +1 -0
  22. package/dist/Image/imageNode.js +16 -0
  23. package/dist/Image/index.cjs +1 -0
  24. package/dist/Image/index.d.ts +37 -0
  25. package/dist/Image/index.d.ts.map +1 -0
  26. package/dist/Image/index.js +92 -0
  27. package/dist/Leafs/index.cjs +1 -0
  28. package/dist/Leafs/index.d.ts +13 -0
  29. package/dist/Leafs/index.d.ts.map +1 -0
  30. package/dist/Leafs/index.js +66 -0
  31. package/dist/Link/index.cjs +1 -0
  32. package/dist/Link/index.d.ts +25 -0
  33. package/dist/Link/index.d.ts.map +1 -0
  34. package/dist/Link/index.js +148 -0
  35. package/dist/Link/isValidLink.cjs +1 -0
  36. package/dist/Link/isValidLink.d.ts +3 -0
  37. package/dist/Link/isValidLink.d.ts.map +1 -0
  38. package/dist/Link/isValidLink.js +35 -0
  39. package/dist/List/index.cjs +1 -0
  40. package/dist/List/index.d.ts +17 -0
  41. package/dist/List/index.d.ts.map +1 -0
  42. package/dist/List/index.js +151 -0
  43. package/dist/LocalizedQuotationMarks/consume.cjs +1 -0
  44. package/dist/LocalizedQuotationMarks/consume.d.ts +3 -0
  45. package/dist/LocalizedQuotationMarks/consume.d.ts.map +1 -0
  46. package/dist/LocalizedQuotationMarks/consume.js +115 -0
  47. package/dist/LocalizedQuotationMarks/consumes.cjs +1 -0
  48. package/dist/LocalizedQuotationMarks/consumes.d.ts +3 -0
  49. package/dist/LocalizedQuotationMarks/consumes.d.ts.map +1 -0
  50. package/dist/LocalizedQuotationMarks/consumes.js +7 -0
  51. package/dist/LocalizedQuotationMarks/index.cjs +1 -0
  52. package/dist/LocalizedQuotationMarks/index.d.ts +9 -0
  53. package/dist/LocalizedQuotationMarks/index.d.ts.map +1 -0
  54. package/dist/LocalizedQuotationMarks/index.js +13 -0
  55. package/dist/Table/index.cjs +1 -0
  56. package/dist/Table/index.d.ts +16 -0
  57. package/dist/Table/index.d.ts.map +1 -0
  58. package/dist/Table/index.js +179 -0
  59. package/dist/Text/index.cjs +1 -0
  60. package/dist/Text/index.d.ts +25 -0
  61. package/dist/Text/index.d.ts.map +1 -0
  62. package/dist/Text/index.js +64 -0
  63. package/dist/index.cjs +1 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +31 -0
  67. package/dist/shared/ChildPlaceholder.cjs +1 -0
  68. package/dist/shared/ChildPlaceholder.d.ts +12 -0
  69. package/dist/shared/ChildPlaceholder.d.ts.map +1 -0
  70. package/dist/shared/ChildPlaceholder.js +17 -0
  71. package/dist/shared/Field.cjs +1 -0
  72. package/dist/shared/Field.d.ts +14 -0
  73. package/dist/shared/Field.d.ts.map +1 -0
  74. package/dist/shared/Field.js +28 -0
  75. package/dist/shared/hotkey.cjs +1 -0
  76. package/dist/shared/hotkey.d.ts +3 -0
  77. package/dist/shared/hotkey.d.ts.map +1 -0
  78. package/dist/shared/hotkey.js +6 -0
  79. package/dist/shared/icons.cjs +1 -0
  80. package/dist/shared/icons.d.ts +62 -0
  81. package/dist/shared/icons.d.ts.map +1 -0
  82. package/dist/shared/icons.js +153 -0
  83. package/dist/shared/insertBlock.cjs +1 -0
  84. package/dist/shared/insertBlock.d.ts +5 -0
  85. package/dist/shared/insertBlock.d.ts.map +1 -0
  86. package/dist/shared/insertBlock.js +13 -0
  87. package/dist/shared/media.cjs +1 -0
  88. package/dist/shared/media.d.ts +25 -0
  89. package/dist/shared/media.d.ts.map +1 -0
  90. package/dist/shared/media.js +43 -0
  91. package/package.json +67 -4
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2023-2026 Dotvoid AB
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,195 @@
1
- # Temporary Holding Version
1
+ # @dotvoid/textbit-plugins
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
+ Ready to use plugins for [`@dotvoid/textbit`](https://github.com/dotvoid/textbit/tree/main/packages/textbit): headings, formatting,
4
+ lists, links, tables, code, quotes, images and audio.
5
+
6
+ The plugins are headless. They render semantic HTML with `tb-*` classes and no
7
+ styling of their own, apart from what text needs to read right, like bold being
8
+ bold. Add [`@dotvoid/textbit-styles`](https://github.com/dotvoid/textbit/tree/main/packages/styles) for a default look, or style the
9
+ classes yourself.
10
+
11
+ ```bash
12
+ npm install @dotvoid/textbit @dotvoid/textbit-plugins
13
+ ```
14
+
15
+ ```tsx
16
+ import { Textbit } from '@dotvoid/textbit'
17
+ import { Text, Bold, Italic, Link, UnorderedList, OrderedList } from '@dotvoid/textbit-plugins'
18
+
19
+ const plugins = [Text(), Bold(), Italic(), Link(), UnorderedList(), OrderedList()]
20
+
21
+ <Textbit.Root value={value} onChange={setValue} plugins={plugins}>
22
+ <Textbit.Editable aria-label='Article' />
23
+ </Textbit.Root>
24
+ ```
25
+
26
+ Create the plugin list once, for example with `useMemo`, not on every render.
27
+
28
+ ## Only what you import
29
+
30
+ Every plugin is a named export, and the package is built so bundlers keep only
31
+ the plugins you import. Importing `Bold` does not bring `Table` along. The build
32
+ checks this for every plugin against its ES modules output.
33
+
34
+ ## Plugins
35
+
36
+ Every type and mark is named `base/*`, the namespace for textbit's standard
37
+ content. A `base/image` is always this package's image, with this package's
38
+ document format, in every application that uses textbit.
39
+
40
+ So a plugin of your own gets a namespace of your own, also when it does the same
41
+ job as one of these: your own image plugin is `acme/image`, not `base/image`.
42
+ Registered as `base/image`, it would replace this one under the same name with a
43
+ different document format, and documents, copy and paste, and anything else that
44
+ reads `base/image` could no longer tell the two apart. To change how one of these
45
+ plugins looks or behaves, use its options and the styles.
46
+
47
+ The one exception is `base/text`. Textbit itself creates `base/text` blocks, on
48
+ Enter, on paste and in an empty document, so a plugin that renders text in your
49
+ own way has to keep that name and its format: no role for a paragraph, and
50
+ `properties.role` for anything else.
51
+
52
+ | Plugin | Element types | Default shortcut |
53
+ |--------|---------------|------------------|
54
+ | `Text` | `base/text`, role `heading-1` to `heading-6` or `preamble` | Ctrl+Alt+0 to 4 (⌘⌥0 to 4) |
55
+ | `Bold`, `Italic`, `Underline` | marks `base/bold`, `base/italic`, `base/underline` | Ctrl/⌘+B, I, U |
56
+ | `Strikethrough` | mark `base/strikethrough` | none |
57
+ | `Code` | mark `base/code` | none |
58
+ | `UnorderedList`, `OrderedList` | `base/unordered-list`, `base/ordered-list` | Ctrl/⌘+Shift+8, Ctrl/⌘+Shift+7 |
59
+ | `CodeBlock` | `base/code-block` | none |
60
+ | `Table` | `base/table` | none |
61
+ | `Link` | inline `base/link` | Ctrl/⌘+K |
62
+ | `LocalizedQuotationMarks` | none, it changes typed quotes | none |
63
+ | `Image` | `base/image` | none |
64
+ | `Audio` | `base/audio` | none |
65
+ | `Blockquote` | `base/blockquote` | none |
66
+
67
+ `Text` replaces textbit's built-in plain paragraph and keeps its format, adding
68
+ roles for headings and the lead-in.
69
+
70
+ ### Text
71
+
72
+ Paragraphs, headings and a lead-in. A paragraph has no role; a heading has the
73
+ role `heading-1` to `heading-6` and renders as `h1` to `h6`; a lead-in, the
74
+ larger opening paragraph of an article, has the role `preamble` and renders as a
75
+ `p`. The lead-in has no default shortcut.
76
+
77
+ | Option | Type | Description |
78
+ |--------|------|-------------|
79
+ | `headings` | `(1 \| 2 \| 3 \| 4 \| 5 \| 6)[]` | Levels to offer. Default `[1, 2, 3, 4]`. |
80
+ | `preamble` | `boolean` | Offer the lead-in. Default `true`. |
81
+ | `singleLine` | `boolean` | No Enter and no soft breaks, for a title field. |
82
+ | `titles` | `Partial<Record<TextRole, string>>` | Action names, e.g. translations. |
83
+ | `placeholders` | `Partial<Record<TextRole, string>>` | Hint in an empty block, per role. |
84
+ | `hotkeys` | `Partial<Record<TextRole, string \| false>>` | Replace or remove a role's shortcut. |
85
+
86
+ ### Formatting marks
87
+
88
+ `Bold`, `Italic`, `Underline`, `Strikethrough` and `Code` take `{ hotkey, title }`.
89
+ The first four style themselves inline. `Code` renders with the class `tb-code`,
90
+ since a monospace font and a background belong in a stylesheet.
91
+
92
+ ### Lists
93
+
94
+ `UnorderedList` and `OrderedList` take `{ title, placeholder, hotkey }`. Use both
95
+ to switch a list between bullets and numbers. Enter on the last item adds an item;
96
+ Enter on an empty last item leaves the list.
97
+
98
+ ### Code block and table
99
+
100
+ `CodeBlock({ indentSize, title, placeholder, hotkey })`: Tab indents inside the
101
+ block. `Table({ rows, columns, titles })`: Tab and Shift+Tab move between cells,
102
+ and Tab in the last cell adds a row. Its actions insert and delete rows and
103
+ columns; they show in menus while the caret is in a cell.
104
+
105
+ Both use Tab only in their own context, through textbit's `when()` on actions, so
106
+ they work side by side.
107
+
108
+ ### Link
109
+
110
+ `Link({ hotkey, labels })`. Ctrl/⌘+K links the selection and moves to its address
111
+ field; Ctrl/⌘-click opens a link in a new tab. Addresses are checked whenever a
112
+ link is rendered, not only when typed, so a `javascript:` address arriving through
113
+ paste, a collaborator or stored data never becomes a live link. `labels` names the
114
+ address field and buttons, e.g. for translation.
115
+
116
+ ### Localized quotation marks
117
+
118
+ Turns typed `"` and `'` into the quotation marks of the editor's language (“…” in
119
+ English, ”…” in Swedish, «…» in Norwegian), and two hyphens into an en dash.
120
+
121
+ ### Image and audio
122
+
123
+ Reference plugins: complete, and meant to be read when you build your own. Give
124
+ yours a namespace of your own, such as `acme/image` (see [Plugins](#plugins)).
125
+
126
+ ```tsx
127
+ Image({
128
+ upload: async (file) => {
129
+ const { url, width, height } = await myStorage.put(file)
130
+ return { src: url, width, height } // or undefined to drop the file
131
+ }
132
+ })
133
+ ```
134
+
135
+ Dropped and picked files go to your `upload` function, and its answer becomes the
136
+ image. Without `upload` the plugin only renders images and offers no way to add
137
+ one: textbit never stores a file in the document itself. While the upload runs,
138
+ textbit's upload marker shows it, if you place `Textbit.UploadMarker` in the editor.
139
+
140
+ | Option | Description |
141
+ |--------|-------------|
142
+ | `upload` | `(file: File) => Promise<{ src, width?, height?, alt? } \| undefined>` |
143
+ | `accept` | MIME types. Image default: JPEG, PNG, GIF, WebP, AVIF. Audio default: MP3, MP4/AAC, Ogg, WAV, WebM. |
144
+ | `maxSize` | Largest file, in bytes. |
145
+ | `labels` | Action name, field labels and hints, and other text. |
146
+
147
+ Images have alternative text, so screen readers can describe them. It is a text
148
+ field in the document (`base/image/alt`) between the image and its caption: the
149
+ caret moves in and out of it like any other text, and no `<input>` captures
150
+ the keyboard. Images without the field get an empty one when normalized.
151
+
152
+ The caption and the alternative text are labelled fields (`.tb-field`, with
153
+ `.tb-field-label` and `.tb-field-text`); a click on a label puts the caret in its
154
+ field. Only `http`, `https`, `blob` and image or audio `data` addresses are
155
+ rendered.
156
+
157
+ `imageNode()` and `audioNode()` build a complete block, for inserting media you
158
+ already store.
159
+
160
+ ### Blockquote
161
+
162
+ `Blockquote({ labels, hotkey })`: a quote and its source. Leave the source empty
163
+ when there is none.
164
+
165
+ ## Translation
166
+
167
+ The plugins ship English defaults for every visible and announced text: action
168
+ titles, placeholders, labels. Each plugin's options replace them.
169
+
170
+ ```tsx
171
+ Text({ titles: { 'heading-1': 'Rubrik 1' }, placeholders: { 'heading-1': 'Rubrik' } })
172
+ Link({ labels: { title: 'Länk', address: 'Länkadress' } })
173
+ ```
174
+
175
+ ## Shortcuts
176
+
177
+ A shortcut that a browser or operating system already uses never reaches the page,
178
+ so defaults are only set where the key is known to be free. They were checked
179
+ against Chrome's published shortcuts (which Edge shares) and Firefox's developer
180
+ tools.
181
+
182
+ - **No default** for `Code`, `CodeBlock` and `Strikethrough`. The usual keys are
183
+ taken: Ctrl+Shift+C opens the element picker in Chrome and Firefox, and
184
+ Ctrl+Shift+K opens Firefox's web console. Set your own with `hotkey`.
185
+ - **Ctrl/⌘+K** for links overrides Chrome's address bar search on Windows and Linux,
186
+ as Google Docs does; pages are allowed to.
187
+ - Firefox's browser shortcut list could not be checked for the heading and list
188
+ keys. They follow Google Docs.
189
+
190
+ Every plugin takes `hotkey: false` (or `hotkeys` for `Text`) to remove a shortcut.
191
+
192
+ ## Credits
193
+
194
+ Icons are from [Lucide](https://lucide.dev), used under the ISC License; the notice
195
+ is in `lib/shared/icons.tsx`.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function d({src:e,title:t},o=""){return{id:crypto.randomUUID(),class:"block",type:"base/audio",properties:{src:e,title:t??""},children:[{id:crypto.randomUUID(),class:"void",type:"base/audio/content",children:[{text:""}]},{id:crypto.randomUUID(),class:"text",type:"base/audio/caption",children:[{text:o}]}]}}exports.audioNode=d;
@@ -0,0 +1,8 @@
1
+ import { Element } from 'slate';
2
+ export type AudioUpload = {
3
+ src: string;
4
+ title?: string;
5
+ };
6
+ /** A complete audio block, e.g. to insert audio the host already stores */
7
+ export declare function audioNode({ src, title }: AudioUpload, caption?: string): Element;
8
+ //# sourceMappingURL=audioNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioNode.d.ts","sourceRoot":"","sources":["../../lib/Audio/audioNode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAEpC,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,SAAK,GAAG,OAAO,CAW5E"}
@@ -0,0 +1,15 @@
1
+ function d({ src: t, title: e }, o = "") {
2
+ return {
3
+ id: crypto.randomUUID(),
4
+ class: "block",
5
+ type: "base/audio",
6
+ properties: { src: t, title: e ?? "" },
7
+ children: [
8
+ { id: crypto.randomUUID(), class: "void", type: "base/audio/content", children: [{ text: "" }] },
9
+ { id: crypto.randomUUID(), class: "text", type: "base/audio/caption", children: [{ text: o }] }
10
+ ]
11
+ };
12
+ }
13
+ export {
14
+ d as audioNode
15
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),r=require("slate"),d=require("slate-react"),p=require("../shared/icons.cjs"),b=require("../shared/Field.cjs"),l=require("../shared/media.cjs"),x=require("./audioNode.cjs"),u="base/audio",g=["audio/mpeg","audio/mp4","audio/aac","audio/ogg","audio/wav","audio/webm"],f={title:"Audio",caption:"Caption",captionHint:"Add a caption",missing:"Audio not available"},j=(a={})=>{const n={...f,...a.labels},s=a.accept??g,i=l.uploadConsumer({type:u,accept:t=>s.includes(t),maxSize:a.maxSize,upload:a.upload,build:(t,o)=>x.audioNode({...t,title:t.title??o.name})});return{class:"block",name:u,...i?{consumer:i}:{},actions:[{name:"insert",title:n.title,tool:()=>e.jsx(p.AudioQuoteIcon,{}),visibility:()=>[!!i,!!i,!1],handler:t=>l.pickFiles(t,s.join(","))}],componentEntry:{class:"block",component:({children:t})=>e.jsx("figure",{className:"tb-audio",children:t}),children:[{type:"content",class:"void",component:t=>e.jsx(h,{...t,labels:n}),constraints:{min:1,max:1}},{type:"caption",class:"text",asOwnElement:!0,component:({children:t,element:o,attributes:c})=>e.jsx("figcaption",{...c,className:"tb-field tb-audio-caption",children:e.jsx(b.FieldContent,{element:o,label:n.caption,hint:n.captionHint,children:t})}),constraints:{allowBreak:!1,min:1,max:1}}]}}};function h({element:a,children:n,labels:s}){const i=d.useSlateStatic(),t=r.Node.get(i,d.ReactEditor.findPath(i,a).slice(0,-1)),o=r.Element.isElement(t)?t.properties??{}:{},c=l.safeMediaSrc(o.src),m=typeof o.title=="string"?o.title:void 0;return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"tb-audio-content",contentEditable:!1,children:c?e.jsx("audio",{controls:!0,preload:"metadata",src:c,"aria-label":m,style:{width:"100%"}}):e.jsx("div",{className:"tb-audio-missing",children:s.missing})}),n]})}exports.Audio=j;
@@ -0,0 +1,22 @@
1
+ import { TBPluginDefinition } from '@dotvoid/textbit';
2
+ import { AudioUpload } from './audioNode';
3
+ export type AudioLabels = {
4
+ /** The insert action's name in menus and for screen readers */
5
+ title: string;
6
+ /** Label before the caption field */
7
+ caption: string;
8
+ /** Hint in the empty caption field */
9
+ captionHint: string;
10
+ missing: string;
11
+ };
12
+ export type AudioOptions = {
13
+ /** Store a dropped or picked file and return where it now lives; see Image */
14
+ upload?: (file: File) => Promise<AudioUpload | undefined>;
15
+ /** MIME types to accept. Default: MP3, MP4/AAC, Ogg, WAV and WebM audio. */
16
+ accept?: string[];
17
+ maxSize?: number;
18
+ labels?: Partial<AudioLabels>;
19
+ };
20
+ /** An audio player with a caption. A reference for your own, like Image, under your own namespace. */
21
+ export declare const Audio: (options?: AudioOptions) => TBPluginDefinition;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/Audio/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgD,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAMxG,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAIzD,MAAM,MAAM,WAAW,GAAG;IACxB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAA;IACzD,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAC9B,CAAA;AAUD,sGAAsG;AACtG,eAAO,MAAM,KAAK,GAAI,UAAS,YAAiB,KAAG,kBA+ClD,CAAA"}
@@ -0,0 +1,63 @@
1
+ import { jsx as i, jsxs as d, Fragment as m } from "react/jsx-runtime";
2
+ import { Node as p, Element as u } from "slate";
3
+ import { useSlateStatic as f, ReactEditor as b } from "slate-react";
4
+ import { AudioQuoteIcon as g } from "../shared/icons.js";
5
+ import { FieldContent as h } from "../shared/Field.js";
6
+ import { uploadConsumer as E, pickFiles as x, safeMediaSrc as A } from "../shared/media.js";
7
+ import { audioNode as v } from "./audioNode.js";
8
+ const l = "base/audio", y = ["audio/mpeg", "audio/mp4", "audio/aac", "audio/ogg", "audio/wav", "audio/webm"], C = {
9
+ title: "Audio",
10
+ caption: "Caption",
11
+ captionHint: "Add a caption",
12
+ missing: "Audio not available"
13
+ }, B = (a = {}) => {
14
+ const n = { ...C, ...a.labels }, c = a.accept ?? y, e = E({
15
+ type: l,
16
+ accept: (t) => c.includes(t),
17
+ maxSize: a.maxSize,
18
+ upload: a.upload,
19
+ build: (t, o) => v({ ...t, title: t.title ?? o.name })
20
+ });
21
+ return {
22
+ class: "block",
23
+ name: l,
24
+ ...e ? { consumer: e } : {},
25
+ actions: [{
26
+ name: "insert",
27
+ title: n.title,
28
+ tool: () => /* @__PURE__ */ i(g, {}),
29
+ visibility: () => [!!e, !!e, !1],
30
+ handler: (t) => x(t, c.join(","))
31
+ }],
32
+ componentEntry: {
33
+ class: "block",
34
+ component: ({ children: t }) => /* @__PURE__ */ i("figure", { className: "tb-audio", children: t }),
35
+ children: [
36
+ {
37
+ type: "content",
38
+ class: "void",
39
+ component: (t) => /* @__PURE__ */ i(N, { ...t, labels: n }),
40
+ constraints: { min: 1, max: 1 }
41
+ },
42
+ {
43
+ type: "caption",
44
+ class: "text",
45
+ // A figcaption must sit directly in its figure, without a wrapping <div>
46
+ asOwnElement: !0,
47
+ component: ({ children: t, element: o, attributes: s }) => /* @__PURE__ */ i("figcaption", { ...s, className: "tb-field tb-audio-caption", children: /* @__PURE__ */ i(h, { element: o, label: n.caption, hint: n.captionHint, children: t }) }),
48
+ constraints: { allowBreak: !1, min: 1, max: 1 }
49
+ }
50
+ ]
51
+ }
52
+ };
53
+ };
54
+ function N({ element: a, children: n, labels: c }) {
55
+ const e = f(), t = p.get(e, b.findPath(e, a).slice(0, -1)), o = u.isElement(t) ? t.properties ?? {} : {}, s = A(o.src), r = typeof o.title == "string" ? o.title : void 0;
56
+ return /* @__PURE__ */ d(m, { children: [
57
+ /* @__PURE__ */ i("div", { className: "tb-audio-content", contentEditable: !1, children: s ? /* @__PURE__ */ i("audio", { controls: !0, preload: "metadata", src: s, "aria-label": r, style: { width: "100%" } }) : /* @__PURE__ */ i("div", { className: "tb-audio-missing", children: c.missing }) }),
58
+ n
59
+ ] });
60
+ }
61
+ export {
62
+ B as Audio
63
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),a=require("../shared/icons.cjs"),s=require("../shared/ChildPlaceholder.cjs"),u=require("../shared/hotkey.cjs"),i=require("../shared/insertBlock.cjs"),o="base/blockquote",d={title:"Quote",body:"Quote",citation:"Source"},b=(n={})=>{const l={...d,...n.labels};return{class:"block",name:o,actions:[{name:"insert",title:l.title,tool:()=>t.jsx(a.BlockquoteIcon,{}),hotkey:u.hotkeyOption(n.hotkey),visibility:e=>[!0,!0,e.type===o],handler:({editor:e})=>{i.insertBlockAfterCaret(e,{id:crypto.randomUUID(),class:"block",type:o,children:[i.textNode(`${o}/body`),i.textNode(`${o}/citation`)]})}}],componentEntry:{class:"block",component:({children:e})=>t.jsx("figure",{className:"tb-blockquote",children:e}),children:[{type:"body",class:"text",component:({children:e,element:c})=>t.jsxs("blockquote",{className:"tb-blockquote-body",style:{position:"relative",margin:0},children:[t.jsx(s.ChildPlaceholder,{element:c,children:l.body}),e]}),constraints:{min:1,max:1,allowSoftBreak:!0}},{type:"citation",class:"text",asOwnElement:!0,component:({children:e,element:c,attributes:r})=>t.jsxs("figcaption",{...r,className:"tb-blockquote-citation",style:{position:"relative"},children:[t.jsx(s.ChildPlaceholder,{element:c,children:l.citation}),e]}),constraints:{min:1,max:1,allowBreak:!1}}]}}};exports.Blockquote=b;
@@ -0,0 +1,15 @@
1
+ import { TBPluginDefinition } from '@dotvoid/textbit';
2
+ export type BlockquoteLabels = {
3
+ /** The insert action's name in menus and for screen readers */
4
+ title: string;
5
+ body: string;
6
+ citation: string;
7
+ };
8
+ export type BlockquoteOptions = {
9
+ labels?: Partial<BlockquoteLabels>;
10
+ /** A shortcut that inserts a quote. None by default. */
11
+ hotkey?: string | false;
12
+ };
13
+ /** A quotation with its source. The source is optional: leave it empty. */
14
+ export declare const Blockquote: (options?: BlockquoteOptions) => TBPluginDefinition;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/Blockquote/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgD,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAQxG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAClC,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACxB,CAAA;AAID,2EAA2E;AAC3E,eAAO,MAAM,UAAU,GAAI,UAAS,iBAAsB,KAAG,kBAoD5D,CAAA"}
@@ -0,0 +1,56 @@
1
+ import { jsxs as n, jsx as e } from "react/jsx-runtime";
2
+ import { BlockquoteIcon as m } from "../shared/icons.js";
3
+ import { ChildPlaceholder as r } from "../shared/ChildPlaceholder.js";
4
+ import { hotkeyOption as b } from "../shared/hotkey.js";
5
+ import { insertBlockAfterCaret as p, textNode as a } from "../shared/insertBlock.js";
6
+ const o = "base/blockquote", d = { title: "Quote", body: "Quote", citation: "Source" }, x = (c = {}) => {
7
+ const l = { ...d, ...c.labels };
8
+ return {
9
+ class: "block",
10
+ name: o,
11
+ actions: [{
12
+ name: "insert",
13
+ title: l.title,
14
+ tool: () => /* @__PURE__ */ e(m, {}),
15
+ hotkey: b(c.hotkey),
16
+ visibility: (t) => [!0, !0, t.type === o],
17
+ handler: ({ editor: t }) => {
18
+ p(t, {
19
+ id: crypto.randomUUID(),
20
+ class: "block",
21
+ type: o,
22
+ children: [a(`${o}/body`), a(`${o}/citation`)]
23
+ });
24
+ }
25
+ }],
26
+ componentEntry: {
27
+ class: "block",
28
+ component: ({ children: t }) => /* @__PURE__ */ e("figure", { className: "tb-blockquote", children: t }),
29
+ children: [
30
+ {
31
+ type: "body",
32
+ class: "text",
33
+ component: ({ children: t, element: i }) => /* @__PURE__ */ n("blockquote", { className: "tb-blockquote-body", style: { position: "relative", margin: 0 }, children: [
34
+ /* @__PURE__ */ e(r, { element: i, children: l.body }),
35
+ t
36
+ ] }),
37
+ constraints: { min: 1, max: 1, allowSoftBreak: !0 }
38
+ },
39
+ {
40
+ type: "citation",
41
+ class: "text",
42
+ // A figcaption must sit directly in its figure, without a wrapping <div>
43
+ asOwnElement: !0,
44
+ component: ({ children: t, element: i, attributes: s }) => /* @__PURE__ */ n("figcaption", { ...s, className: "tb-blockquote-citation", style: { position: "relative" }, children: [
45
+ /* @__PURE__ */ e(r, { element: i, children: l.citation }),
46
+ t
47
+ ] }),
48
+ constraints: { min: 1, max: 1, allowBreak: !1 }
49
+ }
50
+ ]
51
+ }
52
+ };
53
+ };
54
+ export {
55
+ x as Blockquote
56
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),c=require("slate"),i=require("../shared/icons.cjs"),a=require("../shared/ChildPlaceholder.cjs"),d=require("../shared/hotkey.cjs"),s=require("../shared/insertBlock.cjs"),n="base/code-block",y=(t={})=>({class:"block",name:n,actions:[{name:"insert",title:t.title??"Code block",tool:()=>l.jsx(i.CodeBlockIcon,{}),hotkey:d.hotkeyOption(t.hotkey),visibility:e=>[!0,!0,e.type===n],handler:({editor:e})=>{s.insertBlockAfterCaret(e,{id:crypto.randomUUID(),class:"block",type:n,children:[s.textNode(`${n}/body`)]})}},{name:"indent",hotkey:"tab",when:k,visibility:()=>[!1,!1,!1],handler:({editor:e,event:o})=>{o?.preventDefault(),e.insertText(" ".repeat(t.indentSize??2))}}],componentEntry:{class:"block",component:({children:e})=>l.jsx("div",{className:"tb-code-block",children:e}),children:[{type:"body",class:"text",component:({children:e,element:o})=>l.jsxs("pre",{className:"tb-code-block-body",style:{position:"relative",margin:0,whiteSpace:"pre-wrap"},children:[l.jsx(a.ChildPlaceholder,{element:o,children:t.placeholder}),l.jsx("code",{children:e})]}),constraints:{min:1,max:1,allowBreak:!0,allowSoftBreak:!1,allowExitBreak:!1}}]}});function k(t){const{selection:e}=t;if(!e||!c.Range.isCollapsed(e))return!1;const[o]=c.Editor.nodes(t,{match:r=>c.Element.isElement(r)&&r.type===`${n}/body`});return!!o}exports.CodeBlock=y;
@@ -0,0 +1,14 @@
1
+ import { TBPluginDefinition } from '@dotvoid/textbit';
2
+ export type CodeBlockOptions = {
3
+ /** Spaces Tab inserts. Default: 2. */
4
+ indentSize?: number;
5
+ /** The action's name in menus and for screen readers, e.g. a translation */
6
+ title?: string;
7
+ /** Hint in an empty code block */
8
+ placeholder?: string;
9
+ /** A shortcut that inserts a code block. None by default. */
10
+ hotkey?: string | false;
11
+ };
12
+ /** A block of preformatted code. Tab indents inside it. */
13
+ export declare const CodeBlock: (options?: CodeBlockOptions) => TBPluginDefinition;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/CodeBlock/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoB,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAS5E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACxB,CAAA;AAED,2DAA2D;AAC3D,eAAO,MAAM,SAAS,GAAI,UAAS,gBAAqB,KAAG,kBAmDzD,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { jsxs as a, jsx as r } from "react/jsx-runtime";
2
+ import { Range as i, Editor as c, Element as s } from "slate";
3
+ import { CodeBlockIcon as d } from "../shared/icons.js";
4
+ import { ChildPlaceholder as m } from "../shared/ChildPlaceholder.js";
5
+ import { hotkeyOption as p } from "../shared/hotkey.js";
6
+ import { insertBlockAfterCaret as y, textNode as b } from "../shared/insertBlock.js";
7
+ const l = "base/code-block", w = (t = {}) => ({
8
+ class: "block",
9
+ name: l,
10
+ actions: [
11
+ {
12
+ name: "insert",
13
+ title: t.title ?? "Code block",
14
+ tool: () => /* @__PURE__ */ r(d, {}),
15
+ hotkey: p(t.hotkey),
16
+ visibility: (e) => [!0, !0, e.type === l],
17
+ handler: ({ editor: e }) => {
18
+ y(e, {
19
+ id: crypto.randomUUID(),
20
+ class: "block",
21
+ type: l,
22
+ children: [b(`${l}/body`)]
23
+ });
24
+ }
25
+ },
26
+ {
27
+ name: "indent",
28
+ hotkey: "tab",
29
+ when: f,
30
+ visibility: () => [!1, !1, !1],
31
+ handler: ({ editor: e, event: o }) => {
32
+ o?.preventDefault(), e.insertText(" ".repeat(t.indentSize ?? 2));
33
+ }
34
+ }
35
+ ],
36
+ componentEntry: {
37
+ class: "block",
38
+ component: ({ children: e }) => /* @__PURE__ */ r("div", { className: "tb-code-block", children: e }),
39
+ children: [{
40
+ type: "body",
41
+ class: "text",
42
+ component: ({ children: e, element: o }) => /* @__PURE__ */ a("pre", { className: "tb-code-block-body", style: { position: "relative", margin: 0, whiteSpace: "pre-wrap" }, children: [
43
+ /* @__PURE__ */ r(m, { element: o, children: t.placeholder }),
44
+ /* @__PURE__ */ r("code", { children: e })
45
+ ] }),
46
+ constraints: {
47
+ min: 1,
48
+ max: 1,
49
+ allowBreak: !0,
50
+ allowSoftBreak: !1,
51
+ allowExitBreak: !1
52
+ }
53
+ }]
54
+ }
55
+ });
56
+ function f(t) {
57
+ const { selection: e } = t;
58
+ if (!e || !i.isCollapsed(e))
59
+ return !1;
60
+ const [o] = c.nodes(t, { match: (n) => s.isElement(n) && n.type === `${l}/body` });
61
+ return !!o;
62
+ }
63
+ export {
64
+ w as CodeBlock
65
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function i({src:a,width:e,height:t,alt:o},r=""){return{id:crypto.randomUUID(),class:"block",type:"base/image",properties:{src:a,...e?{width:e}:{},...t?{height:t}:{}},children:[{id:crypto.randomUUID(),class:"void",type:"base/image/content",children:[{text:""}]},{id:crypto.randomUUID(),class:"text",type:"base/image/alt",children:[{text:o??""}]},{id:crypto.randomUUID(),class:"text",type:"base/image/caption",children:[{text:r}]}]}}exports.imageNode=i;
@@ -0,0 +1,11 @@
1
+ import { Element } from 'slate';
2
+ export type ImageUpload = {
3
+ src: string;
4
+ width?: number;
5
+ height?: number;
6
+ /** Alternative text, if the host already has one */
7
+ alt?: string;
8
+ };
9
+ /** A complete image block, e.g. to insert an image the host already stores */
10
+ export declare function imageNode({ src, width, height, alt }: ImageUpload, caption?: string): Element;
11
+ //# sourceMappingURL=imageNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageNode.d.ts","sourceRoot":"","sources":["../../lib/Image/imageNode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAEpC,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,WAAW,EAAE,OAAO,SAAK,GAAG,OAAO,CAYzF"}
@@ -0,0 +1,16 @@
1
+ function o({ src: a, width: e, height: t, alt: r }, c = "") {
2
+ return {
3
+ id: crypto.randomUUID(),
4
+ class: "block",
5
+ type: "base/image",
6
+ properties: { src: a, ...e ? { width: e } : {}, ...t ? { height: t } : {} },
7
+ children: [
8
+ { id: crypto.randomUUID(), class: "void", type: "base/image/content", children: [{ text: "" }] },
9
+ { id: crypto.randomUUID(), class: "text", type: "base/image/alt", children: [{ text: r ?? "" }] },
10
+ { id: crypto.randomUUID(), class: "text", type: "base/image/caption", children: [{ text: c }] }
11
+ ]
12
+ };
13
+ }
14
+ export {
15
+ o as imageNode
16
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),l=require("slate"),r=require("slate-react"),b=require("../shared/icons.cjs"),p=require("../shared/Field.cjs"),m=require("../shared/media.cjs"),f=require("./imageNode.cjs"),d="base/image",x=["image/jpeg","image/png","image/gif","image/webp","image/avif"],j={title:"Image",caption:"Caption",captionHint:"Add a caption",alt:"Alt text",altHint:"Describe the image for those who cannot see it",missing:"Image not available"},y=(i={})=>{const a={...j,...i.labels},c=i.accept??x,n=m.uploadConsumer({type:d,accept:e=>c.includes(e),maxSize:i.maxSize,upload:i.upload,build:e=>f.imageNode(e)});return{class:"block",name:d,...n?{consumer:n}:{},actions:[{name:"insert",title:a.title,tool:()=>t.jsx(b.ImageIcon,{}),visibility:()=>[!!n,!!n,!1],handler:e=>m.pickFiles(e,c.join(","))}],componentEntry:{class:"block",component:({children:e})=>t.jsx("figure",{className:"tb-image",children:e}),children:[{type:"content",class:"void",component:e=>t.jsx(E,{...e,labels:a}),constraints:{min:1,max:1}},{type:"alt",class:"text",component:({children:e,element:o})=>t.jsx("div",{className:"tb-field tb-image-alt",children:t.jsx(p.FieldContent,{element:o,label:a.alt,hint:a.altHint,children:e})}),constraints:{allowBreak:!1,min:1,max:1}},{type:"caption",class:"text",asOwnElement:!0,component:({children:e,element:o,attributes:s})=>t.jsx("figcaption",{...s,className:"tb-field tb-image-caption",children:t.jsx(p.FieldContent,{element:o,label:a.caption,hint:a.captionHint,children:e})}),constraints:{allowBreak:!1,min:1,max:1}}]}}};function E({element:i,children:a,labels:c}){const n=r.useSlateStatic(),e=r.ReactEditor.findPath(n,i),o=l.Node.get(n,e.slice(0,-1)),s=l.Element.isElement(o)?o.properties??{}:{},g=m.safeMediaSrc(s.src),u=r.useSlateSelector(h=>v(h,i));return t.jsxs(t.Fragment,{children:[t.jsx("div",{className:"tb-image-content",contentEditable:!1,draggable:"true",children:g?t.jsx("img",{src:g,alt:u,width:typeof s.width=="number"?s.width:void 0,height:typeof s.height=="number"?s.height:void 0,style:{display:"block",maxWidth:"100%",height:"auto"}}):t.jsx("div",{className:"tb-image-missing",role:"img","aria-label":c.missing,children:c.missing})}),a]})}function v(i,a){try{const n=l.Node.parent(i,r.ReactEditor.findPath(i,a)).children.find(e=>l.Element.isElement(e)&&e.type===`${d}/alt`);return n?l.Node.string(n):""}catch{return""}}exports.Image=y;
@@ -0,0 +1,37 @@
1
+ import { TBPluginDefinition } from '@dotvoid/textbit';
2
+ import { ImageUpload } from './imageNode';
3
+ export type ImageLabels = {
4
+ /** The insert action's name in menus and for screen readers */
5
+ title: string;
6
+ /** Label before the caption field */
7
+ caption: string;
8
+ /** Hint in the empty caption field */
9
+ captionHint: string;
10
+ /** Label before the alternative text field */
11
+ alt: string;
12
+ /** Hint in the empty alternative text field */
13
+ altHint: string;
14
+ missing: string;
15
+ };
16
+ export type ImageOptions = {
17
+ /**
18
+ * Store a dropped or picked file and return where it now lives. Return
19
+ * undefined to drop the file, e.g. when the user cancels. Without it the
20
+ * plugin only renders images; it cannot add them.
21
+ */
22
+ upload?: (file: File) => Promise<ImageUpload | undefined>;
23
+ /** MIME types to accept. Default: JPEG, PNG, GIF, WebP and AVIF. */
24
+ accept?: string[];
25
+ /** Largest file to accept, in bytes */
26
+ maxSize?: number;
27
+ /** Labels and hints, e.g. translations */
28
+ labels?: Partial<ImageLabels>;
29
+ };
30
+ /**
31
+ * An image with a caption and alternative text. Files reach it by drop or from
32
+ * the insert action, and are stored by your `upload` function. Also a reference
33
+ * for building your own, which gets a namespace of your own (`acme/image`, not
34
+ * `base/image`).
35
+ */
36
+ export declare const Image: (options?: ImageOptions) => TBPluginDefinition;
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/Image/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgD,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAMxG,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAIzD,MAAM,MAAM,WAAW,GAAG;IACxB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAA;IACX,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAA;IACzD,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAC9B,CAAA;AAYD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,UAAS,YAAiB,KAAG,kBA6DlD,CAAA"}