@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.
- package/LICENSE +7 -0
- package/README.md +194 -2
- package/dist/Audio/audioNode.cjs +1 -0
- package/dist/Audio/audioNode.d.ts +8 -0
- package/dist/Audio/audioNode.d.ts.map +1 -0
- package/dist/Audio/audioNode.js +15 -0
- package/dist/Audio/index.cjs +1 -0
- package/dist/Audio/index.d.ts +22 -0
- package/dist/Audio/index.d.ts.map +1 -0
- package/dist/Audio/index.js +63 -0
- package/dist/Blockquote/index.cjs +1 -0
- package/dist/Blockquote/index.d.ts +15 -0
- package/dist/Blockquote/index.d.ts.map +1 -0
- package/dist/Blockquote/index.js +56 -0
- package/dist/CodeBlock/index.cjs +1 -0
- package/dist/CodeBlock/index.d.ts +14 -0
- package/dist/CodeBlock/index.d.ts.map +1 -0
- package/dist/CodeBlock/index.js +65 -0
- package/dist/Image/imageNode.cjs +1 -0
- package/dist/Image/imageNode.d.ts +11 -0
- package/dist/Image/imageNode.d.ts.map +1 -0
- package/dist/Image/imageNode.js +16 -0
- package/dist/Image/index.cjs +1 -0
- package/dist/Image/index.d.ts +37 -0
- package/dist/Image/index.d.ts.map +1 -0
- package/dist/Image/index.js +92 -0
- package/dist/Leafs/index.cjs +1 -0
- package/dist/Leafs/index.d.ts +13 -0
- package/dist/Leafs/index.d.ts.map +1 -0
- package/dist/Leafs/index.js +66 -0
- package/dist/Link/index.cjs +1 -0
- package/dist/Link/index.d.ts +25 -0
- package/dist/Link/index.d.ts.map +1 -0
- package/dist/Link/index.js +148 -0
- package/dist/Link/isValidLink.cjs +1 -0
- package/dist/Link/isValidLink.d.ts +3 -0
- package/dist/Link/isValidLink.d.ts.map +1 -0
- package/dist/Link/isValidLink.js +35 -0
- package/dist/List/index.cjs +1 -0
- package/dist/List/index.d.ts +17 -0
- package/dist/List/index.d.ts.map +1 -0
- package/dist/List/index.js +151 -0
- package/dist/LocalizedQuotationMarks/consume.cjs +1 -0
- package/dist/LocalizedQuotationMarks/consume.d.ts +3 -0
- package/dist/LocalizedQuotationMarks/consume.d.ts.map +1 -0
- package/dist/LocalizedQuotationMarks/consume.js +115 -0
- package/dist/LocalizedQuotationMarks/consumes.cjs +1 -0
- package/dist/LocalizedQuotationMarks/consumes.d.ts +3 -0
- package/dist/LocalizedQuotationMarks/consumes.d.ts.map +1 -0
- package/dist/LocalizedQuotationMarks/consumes.js +7 -0
- package/dist/LocalizedQuotationMarks/index.cjs +1 -0
- package/dist/LocalizedQuotationMarks/index.d.ts +9 -0
- package/dist/LocalizedQuotationMarks/index.d.ts.map +1 -0
- package/dist/LocalizedQuotationMarks/index.js +13 -0
- package/dist/Table/index.cjs +1 -0
- package/dist/Table/index.d.ts +16 -0
- package/dist/Table/index.d.ts.map +1 -0
- package/dist/Table/index.js +179 -0
- package/dist/Text/index.cjs +1 -0
- package/dist/Text/index.d.ts +25 -0
- package/dist/Text/index.d.ts.map +1 -0
- package/dist/Text/index.js +64 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/shared/ChildPlaceholder.cjs +1 -0
- package/dist/shared/ChildPlaceholder.d.ts +12 -0
- package/dist/shared/ChildPlaceholder.d.ts.map +1 -0
- package/dist/shared/ChildPlaceholder.js +17 -0
- package/dist/shared/Field.cjs +1 -0
- package/dist/shared/Field.d.ts +14 -0
- package/dist/shared/Field.d.ts.map +1 -0
- package/dist/shared/Field.js +28 -0
- package/dist/shared/hotkey.cjs +1 -0
- package/dist/shared/hotkey.d.ts +3 -0
- package/dist/shared/hotkey.d.ts.map +1 -0
- package/dist/shared/hotkey.js +6 -0
- package/dist/shared/icons.cjs +1 -0
- package/dist/shared/icons.d.ts +62 -0
- package/dist/shared/icons.d.ts.map +1 -0
- package/dist/shared/icons.js +153 -0
- package/dist/shared/insertBlock.cjs +1 -0
- package/dist/shared/insertBlock.d.ts +5 -0
- package/dist/shared/insertBlock.d.ts.map +1 -0
- package/dist/shared/insertBlock.js +13 -0
- package/dist/shared/media.cjs +1 -0
- package/dist/shared/media.d.ts +25 -0
- package/dist/shared/media.d.ts.map +1 -0
- package/dist/shared/media.js +43 -0
- 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
|
-
#
|
|
1
|
+
# @dotvoid/textbit-plugins
|
|
2
2
|
|
|
3
|
-
|
|
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"}
|