@sckyzo/cosmos-react 0.5.0 → 0.6.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/CHANGELOG.md +65 -0
- package/README.md +2 -2
- package/dist/editor/index.cjs +421 -66
- package/dist/editor/index.cjs.map +1 -1
- package/dist/editor/index.d.cts +38 -12
- package/dist/editor/index.d.ts +38 -12
- package/dist/editor/index.js +423 -69
- package/dist/editor/index.js.map +1 -1
- package/dist/index.cjs +5474 -1602
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1413 -167
- package/dist/index.d.ts +1413 -167
- package/dist/index.js +5452 -1604
- package/dist/index.js.map +1 -1
- package/dist/qr/index.cjs +62 -0
- package/dist/qr/index.cjs.map +1 -0
- package/dist/qr/index.d.cts +43 -0
- package/dist/qr/index.d.ts +43 -0
- package/dist/qr/index.js +60 -0
- package/dist/qr/index.js.map +1 -0
- package/package.json +39 -11
package/dist/editor/index.cjs
CHANGED
|
@@ -8,8 +8,11 @@ var react$1 = require('@tiptap/react');
|
|
|
8
8
|
var menus = require('@tiptap/react/menus');
|
|
9
9
|
var extensions = require('@tiptap/extensions');
|
|
10
10
|
var StarterKit = require('@tiptap/starter-kit');
|
|
11
|
+
var TextAlign = require('@tiptap/extension-text-align');
|
|
11
12
|
var jsxRuntime = require('react/jsx-runtime');
|
|
12
13
|
var lucideReact = require('lucide-react');
|
|
14
|
+
var extensionImage = require('@tiptap/extension-image');
|
|
15
|
+
var model = require('@tiptap/pm/model');
|
|
13
16
|
var state = require('@tiptap/pm/state');
|
|
14
17
|
var view = require('@tiptap/pm/view');
|
|
15
18
|
var cosmosReact = require('@sckyzo/cosmos-react');
|
|
@@ -17,6 +20,7 @@ var cosmosReact = require('@sckyzo/cosmos-react');
|
|
|
17
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
21
|
|
|
19
22
|
var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
|
|
23
|
+
var TextAlign__default = /*#__PURE__*/_interopDefault(TextAlign);
|
|
20
24
|
|
|
21
25
|
// src/editor/RichTextEditor.tsx
|
|
22
26
|
function useControllableState({
|
|
@@ -130,26 +134,86 @@ var RequiredMark = ({ srText }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fr
|
|
|
130
134
|
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "ms-0.5 text-red-700 dark:text-red-400", children: "*" }),
|
|
131
135
|
srText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: srText })
|
|
132
136
|
] });
|
|
137
|
+
var isSafeSource = (src) => typeof src === "string" && isHttpUrl(src);
|
|
138
|
+
var RichTextImage = extensionImage.Image.extend({
|
|
139
|
+
// Written out in full: `extend` calls `addOptions` again, and a `configure` before it is lost.
|
|
140
|
+
addOptions() {
|
|
141
|
+
return { inline: true, allowBase64: false, HTMLAttributes: {}, resize: false };
|
|
142
|
+
},
|
|
143
|
+
addAttributes() {
|
|
144
|
+
return {
|
|
145
|
+
src: {
|
|
146
|
+
default: null,
|
|
147
|
+
renderHTML: ({ src }) => isSafeSource(src) ? { src } : {}
|
|
148
|
+
},
|
|
149
|
+
// `alt=""` is kept, as it says the image is decorative: no `alt` says nothing.
|
|
150
|
+
alt: { default: null }
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
parseHTML() {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
tag: "img[src]",
|
|
157
|
+
getAttrs: (element) => isSafeSource(element.getAttribute("src")) ? null : false
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
},
|
|
161
|
+
addInputRules() {
|
|
162
|
+
return [];
|
|
163
|
+
},
|
|
164
|
+
addProseMirrorPlugins() {
|
|
165
|
+
const { schema } = this.editor;
|
|
166
|
+
const rules = model.DOMParser.fromSchema(schema).rules.filter(
|
|
167
|
+
(rule) => !("node" in rule && rule.node === this.name)
|
|
168
|
+
);
|
|
169
|
+
return [new state.Plugin({ props: { clipboardParser: new model.DOMParser(schema, rules) } })];
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
var selectedImage = (editor) => {
|
|
173
|
+
const { selection } = editor.state;
|
|
174
|
+
if (!(selection instanceof state.NodeSelection) || selection.node.type.name !== RichTextImage.name) {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
const { src, alt } = selection.node.attrs;
|
|
178
|
+
return { src: src ?? "", alt };
|
|
179
|
+
};
|
|
180
|
+
var imagePosition = (editor) => {
|
|
181
|
+
const end = state.Selection.near(editor.state.selection.$to, -1).$to;
|
|
182
|
+
const image = editor.schema.nodes[RichTextImage.name];
|
|
183
|
+
return image && end.parent.canReplaceWith(end.index(), end.index(), image) ? end.pos : void 0;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/editor/actions.ts
|
|
133
187
|
var RICH_TEXT_ACTIONS = [
|
|
134
188
|
"bold",
|
|
135
189
|
"italic",
|
|
190
|
+
"underline",
|
|
136
191
|
"strike",
|
|
137
192
|
"heading",
|
|
138
193
|
"bulletList",
|
|
139
194
|
"orderedList",
|
|
140
195
|
"blockquote",
|
|
141
196
|
"code",
|
|
197
|
+
"codeBlock",
|
|
198
|
+
"align",
|
|
142
199
|
"link",
|
|
200
|
+
"image",
|
|
143
201
|
"undo",
|
|
144
202
|
"redo"
|
|
145
203
|
];
|
|
146
204
|
var BUBBLE_ACTIONS = [
|
|
147
205
|
"bold",
|
|
148
206
|
"italic",
|
|
207
|
+
"underline",
|
|
149
208
|
"strike",
|
|
150
209
|
"code",
|
|
151
210
|
"link"
|
|
152
211
|
];
|
|
212
|
+
var HEADINGS = [
|
|
213
|
+
{ id: "heading1", level: 1, icon: lucideReact.Heading1 },
|
|
214
|
+
{ id: "heading2", level: 2, icon: lucideReact.Heading2 },
|
|
215
|
+
{ id: "heading3", level: 3, icon: lucideReact.Heading3 }
|
|
216
|
+
];
|
|
153
217
|
var ACTION_SPECS = {
|
|
154
218
|
bold: {
|
|
155
219
|
icon: lucideReact.Bold,
|
|
@@ -165,6 +229,13 @@ var ACTION_SPECS = {
|
|
|
165
229
|
isActive: (editor) => editor.isActive("italic"),
|
|
166
230
|
run: (editor) => editor.chain().toggleItalic().run()
|
|
167
231
|
},
|
|
232
|
+
underline: {
|
|
233
|
+
icon: lucideReact.Underline,
|
|
234
|
+
label: "Underline",
|
|
235
|
+
shortcut: ["Mod", "U"],
|
|
236
|
+
isActive: (editor) => editor.isActive("underline"),
|
|
237
|
+
run: (editor) => editor.chain().toggleUnderline().run()
|
|
238
|
+
},
|
|
168
239
|
strike: {
|
|
169
240
|
icon: lucideReact.Strikethrough,
|
|
170
241
|
label: "Strikethrough",
|
|
@@ -175,9 +246,27 @@ var ACTION_SPECS = {
|
|
|
175
246
|
heading: {
|
|
176
247
|
icon: lucideReact.Heading2,
|
|
177
248
|
label: "Heading",
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
249
|
+
isActive: (editor) => editor.isActive("heading"),
|
|
250
|
+
menu: {
|
|
251
|
+
rest: "paragraph",
|
|
252
|
+
current: (editor) => HEADINGS.find(({ level }) => editor.isActive("heading", { level }))?.id ?? "paragraph",
|
|
253
|
+
choices: [
|
|
254
|
+
{
|
|
255
|
+
id: "paragraph",
|
|
256
|
+
label: "Paragraph",
|
|
257
|
+
icon: lucideReact.Pilcrow,
|
|
258
|
+
shortcut: ["Mod", "Alt", "0"],
|
|
259
|
+
run: (editor) => editor.chain().focus().setParagraph().run()
|
|
260
|
+
},
|
|
261
|
+
...HEADINGS.map(({ id, level, icon }) => ({
|
|
262
|
+
id,
|
|
263
|
+
label: `Heading ${level}`,
|
|
264
|
+
icon,
|
|
265
|
+
shortcut: ["Mod", "Alt", String(level)],
|
|
266
|
+
run: (editor) => editor.chain().focus().setHeading({ level }).run()
|
|
267
|
+
}))
|
|
268
|
+
]
|
|
269
|
+
}
|
|
181
270
|
},
|
|
182
271
|
bulletList: {
|
|
183
272
|
icon: lucideReact.List,
|
|
@@ -210,11 +299,60 @@ var ACTION_SPECS = {
|
|
|
210
299
|
isActive: (editor) => editor.isActive("code"),
|
|
211
300
|
run: (editor) => editor.chain().toggleCode().run()
|
|
212
301
|
},
|
|
302
|
+
codeBlock: {
|
|
303
|
+
icon: lucideReact.SquareCode,
|
|
304
|
+
label: "Code block",
|
|
305
|
+
shortcut: ["Mod", "Alt", "C"],
|
|
306
|
+
isActive: (editor) => editor.isActive("codeBlock"),
|
|
307
|
+
run: (editor) => editor.chain().toggleCodeBlock().run()
|
|
308
|
+
},
|
|
309
|
+
align: {
|
|
310
|
+
icon: lucideReact.TextAlignStart,
|
|
311
|
+
mirror: true,
|
|
312
|
+
label: "Text alignment",
|
|
313
|
+
isActive: (editor) => editor.isActive({ textAlign: "center" }) || editor.isActive({ textAlign: "end" }),
|
|
314
|
+
menu: {
|
|
315
|
+
rest: "alignStart",
|
|
316
|
+
current: (editor) => editor.isActive({ textAlign: "center" }) ? "alignCenter" : editor.isActive({ textAlign: "end" }) ? "alignEnd" : "alignStart",
|
|
317
|
+
choices: [
|
|
318
|
+
{
|
|
319
|
+
id: "alignStart",
|
|
320
|
+
label: "Align start",
|
|
321
|
+
icon: lucideReact.TextAlignStart,
|
|
322
|
+
mirror: true,
|
|
323
|
+
// The start is what a block has when nothing is set.
|
|
324
|
+
run: (editor) => editor.chain().focus().unsetTextAlign().run()
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "alignCenter",
|
|
328
|
+
label: "Align center",
|
|
329
|
+
icon: lucideReact.TextAlignCenter,
|
|
330
|
+
run: (editor) => editor.chain().focus().setTextAlign("center").run()
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: "alignEnd",
|
|
334
|
+
label: "Align end",
|
|
335
|
+
icon: lucideReact.TextAlignEnd,
|
|
336
|
+
mirror: true,
|
|
337
|
+
run: (editor) => editor.chain().focus().setTextAlign("end").run()
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
},
|
|
213
342
|
link: {
|
|
214
343
|
icon: lucideReact.Link,
|
|
215
344
|
label: "Link",
|
|
216
345
|
shortcut: ["Mod", "K"],
|
|
217
|
-
isActive: (editor) => editor.isActive("link")
|
|
346
|
+
isActive: (editor) => editor.isActive("link"),
|
|
347
|
+
form: "link"
|
|
348
|
+
},
|
|
349
|
+
image: {
|
|
350
|
+
icon: lucideReact.Image,
|
|
351
|
+
label: "Image",
|
|
352
|
+
isActive: (editor) => selectedImage(editor) !== void 0,
|
|
353
|
+
// An image lives in a line of text, and a code block has none.
|
|
354
|
+
isIdle: (editor) => selectedImage(editor) === void 0 && imagePosition(editor) === void 0,
|
|
355
|
+
form: "image"
|
|
218
356
|
},
|
|
219
357
|
undo: {
|
|
220
358
|
icon: lucideReact.Undo2,
|
|
@@ -287,6 +425,19 @@ var BlockDirection = core.Extension.create({
|
|
|
287
425
|
];
|
|
288
426
|
}
|
|
289
427
|
});
|
|
428
|
+
|
|
429
|
+
// src/editor/documentJson.ts
|
|
430
|
+
var withoutDefaultAlignment = (node) => {
|
|
431
|
+
const { attrs, content, ...rest } = node;
|
|
432
|
+
const result = { ...rest };
|
|
433
|
+
if (attrs) {
|
|
434
|
+
const { textAlign, ...others } = attrs;
|
|
435
|
+
const kept = textAlign == null ? others : { ...others, textAlign };
|
|
436
|
+
if (Object.keys(kept).length > 0) result.attrs = kept;
|
|
437
|
+
}
|
|
438
|
+
if (content) result.content = content.map(withoutDefaultAlignment);
|
|
439
|
+
return result;
|
|
440
|
+
};
|
|
290
441
|
var directionOf2 = (element, read = (el) => getComputedStyle(el)) => element && read(element).direction === "rtl" ? "rtl" : "ltr";
|
|
291
442
|
|
|
292
443
|
// src/ui/mergeRefs.ts
|
|
@@ -310,6 +461,139 @@ function mergeRefs(...refs) {
|
|
|
310
461
|
};
|
|
311
462
|
}
|
|
312
463
|
|
|
464
|
+
// src/editor/imageUrl.ts
|
|
465
|
+
var normalizeImageUrl = (input) => {
|
|
466
|
+
const value = input.trim();
|
|
467
|
+
return isHttpUrl(value) ? value : void 0;
|
|
468
|
+
};
|
|
469
|
+
var LABEL = "text-xs font-medium text-gray-700 dark:text-gray-300";
|
|
470
|
+
var ERROR = "text-xs text-red-700 dark:text-red-400";
|
|
471
|
+
function ImageForm({ editor, labels, onDone, className }) {
|
|
472
|
+
const urlId = react.useId();
|
|
473
|
+
const altId = react.useId();
|
|
474
|
+
const urlErrorId = react.useId();
|
|
475
|
+
const altErrorId = react.useId();
|
|
476
|
+
const urlRef = react.useRef(null);
|
|
477
|
+
const altRef = react.useRef(null);
|
|
478
|
+
const [selected] = react.useState(() => selectedImage(editor));
|
|
479
|
+
const [src, setSrc] = react.useState(selected?.src ?? "");
|
|
480
|
+
const [alt, setAlt] = react.useState(selected?.alt ?? "");
|
|
481
|
+
const [decorative, setDecorative] = react.useState(selected?.alt === "");
|
|
482
|
+
const [invalid, setInvalid] = react.useState(null);
|
|
483
|
+
const apply = () => {
|
|
484
|
+
const address = normalizeImageUrl(src);
|
|
485
|
+
if (!address) {
|
|
486
|
+
setInvalid("url");
|
|
487
|
+
urlRef.current?.focus();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const description = decorative ? "" : alt.trim();
|
|
491
|
+
if (!decorative && !description) {
|
|
492
|
+
setInvalid("alt");
|
|
493
|
+
altRef.current?.focus();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const attrs = { src: address, alt: description };
|
|
497
|
+
const chain = editor.chain().focus();
|
|
498
|
+
const at = imagePosition(editor);
|
|
499
|
+
if (selected) chain.updateAttributes("image", attrs);
|
|
500
|
+
else if (at !== void 0) chain.insertContentAt(at, { type: "image", attrs });
|
|
501
|
+
chain.run();
|
|
502
|
+
onDone();
|
|
503
|
+
};
|
|
504
|
+
const onKeyDown = (e) => {
|
|
505
|
+
if (e.key === "Enter") {
|
|
506
|
+
e.preventDefault();
|
|
507
|
+
apply();
|
|
508
|
+
} else if (e.key === "Escape") {
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
e.stopPropagation();
|
|
511
|
+
onDone();
|
|
512
|
+
editor.commands.focus();
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const input = (side) => clsx.clsx(
|
|
516
|
+
"block w-full",
|
|
517
|
+
fieldClass({ size: "sm", invalid: invalid === side, fixedHeight: false }),
|
|
518
|
+
FIELD_PAD_X.sm,
|
|
519
|
+
FIELD_PAD_Y.sm,
|
|
520
|
+
FIELD_PLACEHOLDER
|
|
521
|
+
);
|
|
522
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
523
|
+
"div",
|
|
524
|
+
{
|
|
525
|
+
role: "group",
|
|
526
|
+
"aria-label": labels.imageDialog,
|
|
527
|
+
className: clsx.clsx("flex flex-col gap-3", className),
|
|
528
|
+
children: [
|
|
529
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
530
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: urlId, className: LABEL, children: labels.imageUrl }),
|
|
531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
532
|
+
"input",
|
|
533
|
+
{
|
|
534
|
+
ref: urlRef,
|
|
535
|
+
id: urlId,
|
|
536
|
+
type: "text",
|
|
537
|
+
inputMode: "url",
|
|
538
|
+
autoFocus: true,
|
|
539
|
+
autoComplete: "off",
|
|
540
|
+
spellCheck: false,
|
|
541
|
+
dir: "ltr",
|
|
542
|
+
"aria-invalid": invalid === "url" || void 0,
|
|
543
|
+
"aria-describedby": invalid === "url" ? urlErrorId : void 0,
|
|
544
|
+
placeholder: "https://",
|
|
545
|
+
value: src,
|
|
546
|
+
onChange: (e) => {
|
|
547
|
+
setSrc(e.target.value);
|
|
548
|
+
setInvalid(null);
|
|
549
|
+
},
|
|
550
|
+
onKeyDown,
|
|
551
|
+
className: input("url")
|
|
552
|
+
}
|
|
553
|
+
),
|
|
554
|
+
invalid === "url" && /* @__PURE__ */ jsxRuntime.jsx("span", { id: urlErrorId, className: ERROR, children: labels.imageInvalid })
|
|
555
|
+
] }),
|
|
556
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
557
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: altId, className: LABEL, children: labels.imageAlt }),
|
|
558
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
559
|
+
"input",
|
|
560
|
+
{
|
|
561
|
+
ref: altRef,
|
|
562
|
+
id: altId,
|
|
563
|
+
type: "text",
|
|
564
|
+
autoComplete: "off",
|
|
565
|
+
disabled: decorative,
|
|
566
|
+
"aria-invalid": invalid === "alt" || void 0,
|
|
567
|
+
"aria-describedby": invalid === "alt" ? altErrorId : void 0,
|
|
568
|
+
value: alt,
|
|
569
|
+
onChange: (e) => {
|
|
570
|
+
setAlt(e.target.value);
|
|
571
|
+
setInvalid(null);
|
|
572
|
+
},
|
|
573
|
+
onKeyDown,
|
|
574
|
+
className: clsx.clsx(input("alt"), "disabled:cursor-not-allowed disabled:opacity-50")
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
invalid === "alt" && /* @__PURE__ */ jsxRuntime.jsx("span", { id: altErrorId, className: ERROR, children: labels.imageAltRequired })
|
|
578
|
+
] }),
|
|
579
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
580
|
+
cosmosReact.Checkbox,
|
|
581
|
+
{
|
|
582
|
+
size: "sm",
|
|
583
|
+
label: labels.imageDecorative,
|
|
584
|
+
checked: decorative,
|
|
585
|
+
onChange: (checked) => {
|
|
586
|
+
setDecorative(checked);
|
|
587
|
+
setInvalid((was) => was === "alt" ? null : was);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
),
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(cosmosReact.Button, { type: "button", size: "xs", onClick: apply, children: labels.imageApply }) })
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
313
597
|
// src/editor/linkUrl.ts
|
|
314
598
|
var BARE_HOST = /^(?:[a-z0-9-]+\.)+[a-z]{2,}(?:[/?#]\S*)?$/i;
|
|
315
599
|
var EMAIL = /^[^\s@<>"'\\:/]+@(?:[a-z0-9-]+\.)+[a-z]{2,}$/i;
|
|
@@ -406,9 +690,9 @@ function FormattingToolbar({
|
|
|
406
690
|
actions,
|
|
407
691
|
labels,
|
|
408
692
|
disabled,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
693
|
+
form,
|
|
694
|
+
onFormChange,
|
|
695
|
+
formMode,
|
|
412
696
|
onEscape,
|
|
413
697
|
className,
|
|
414
698
|
ref
|
|
@@ -422,7 +706,8 @@ function FormattingToolbar({
|
|
|
422
706
|
action,
|
|
423
707
|
{
|
|
424
708
|
active: ACTION_SPECS[action].isActive(current),
|
|
425
|
-
idle: ACTION_SPECS[action].isIdle?.(current) ?? false
|
|
709
|
+
idle: ACTION_SPECS[action].isIdle?.(current) ?? false,
|
|
710
|
+
choice: ACTION_SPECS[action].menu?.current(current)
|
|
426
711
|
}
|
|
427
712
|
])
|
|
428
713
|
)
|
|
@@ -431,6 +716,7 @@ function FormattingToolbar({
|
|
|
431
716
|
const tabStop = Math.min(stop, actions.length - 1);
|
|
432
717
|
const buttons = () => Array.from(root.current?.querySelectorAll("button[data-rt-action]") ?? []);
|
|
433
718
|
const onKeyDown = (event) => {
|
|
719
|
+
if (!(event.target instanceof Node) || !root.current?.contains(event.target)) return;
|
|
434
720
|
if (event.key === "Escape" && onEscape) {
|
|
435
721
|
event.preventDefault();
|
|
436
722
|
onEscape();
|
|
@@ -445,15 +731,15 @@ function FormattingToolbar({
|
|
|
445
731
|
event.preventDefault();
|
|
446
732
|
list[next]?.focus();
|
|
447
733
|
};
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
{
|
|
734
|
+
const field = (which) => {
|
|
735
|
+
const props = {
|
|
451
736
|
editor,
|
|
452
737
|
labels,
|
|
453
|
-
onDone: () =>
|
|
454
|
-
className:
|
|
455
|
-
}
|
|
456
|
-
|
|
738
|
+
onDone: () => onFormChange(null),
|
|
739
|
+
className: formMode === "inline" ? "w-64 p-2" : void 0
|
|
740
|
+
};
|
|
741
|
+
return which === "link" ? /* @__PURE__ */ jsxRuntime.jsx(LinkForm, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(ImageForm, { ...props });
|
|
742
|
+
};
|
|
457
743
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
458
744
|
"div",
|
|
459
745
|
{
|
|
@@ -463,15 +749,16 @@ function FormattingToolbar({
|
|
|
463
749
|
"aria-orientation": "horizontal",
|
|
464
750
|
onKeyDown,
|
|
465
751
|
className: clsx.clsx("flex flex-wrap items-center gap-0.5", className),
|
|
466
|
-
children:
|
|
752
|
+
children: formMode === "inline" && form ? field(form) : actions.map((action, index) => {
|
|
467
753
|
const spec = ACTION_SPECS[action];
|
|
468
|
-
const { active, idle } = state[action];
|
|
469
|
-
const Icon = spec.icon;
|
|
754
|
+
const { active, idle, choice } = state[action];
|
|
470
755
|
const inert = disabled || idle;
|
|
756
|
+
const shown = active && spec.menu ? spec.menu.choices.find(({ id }) => id === choice) : void 0;
|
|
757
|
+
const Icon = shown?.icon ?? spec.icon;
|
|
471
758
|
const common = {
|
|
472
759
|
"data-rt-action": action,
|
|
473
760
|
"aria-label": labels[action],
|
|
474
|
-
"aria-keyshortcuts": ariaShortcut(spec.shortcut),
|
|
761
|
+
"aria-keyshortcuts": spec.shortcut && ariaShortcut(spec.shortcut),
|
|
475
762
|
"aria-pressed": active,
|
|
476
763
|
"aria-disabled": inert || void 0,
|
|
477
764
|
tabIndex: index === tabStop ? 0 : -1,
|
|
@@ -480,43 +767,82 @@ function FormattingToolbar({
|
|
|
480
767
|
onMouseDown: (e) => e.preventDefault(),
|
|
481
768
|
className: clsx.clsx(BUTTON, active ? PRESSED : IDLE)
|
|
482
769
|
};
|
|
483
|
-
const icon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
770
|
+
const icon = /* @__PURE__ */ jsxRuntime.jsx(
|
|
771
|
+
Icon,
|
|
772
|
+
{
|
|
773
|
+
"aria-hidden": true,
|
|
774
|
+
className: clsx.clsx(
|
|
775
|
+
"h-4 w-4",
|
|
776
|
+
(shown ? shown.mirror : spec.mirror) && "rtl:-scale-x-100"
|
|
777
|
+
)
|
|
778
|
+
}
|
|
779
|
+
);
|
|
484
780
|
const tooltip = (child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
485
781
|
cosmosReact.Tooltip,
|
|
486
782
|
{
|
|
487
783
|
content: labels[action],
|
|
488
|
-
shortcut: shortcutKeys(spec.shortcut),
|
|
784
|
+
shortcut: spec.shortcut && shortcutKeys(spec.shortcut),
|
|
489
785
|
position: "top",
|
|
490
786
|
children: child
|
|
491
787
|
},
|
|
492
788
|
action
|
|
493
789
|
);
|
|
494
|
-
if (
|
|
495
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(cosmosReact.
|
|
496
|
-
tooltip(
|
|
497
|
-
|
|
498
|
-
|
|
790
|
+
if (spec.menu) {
|
|
791
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(cosmosReact.Menu, { children: [
|
|
792
|
+
tooltip(
|
|
793
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
794
|
+
cosmosReact.Menu.Trigger,
|
|
795
|
+
{
|
|
796
|
+
...common,
|
|
797
|
+
onClick: (e) => inert && e.preventDefault(),
|
|
798
|
+
onKeyDown: (e) => {
|
|
799
|
+
if (inert && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
800
|
+
e.preventDefault();
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
children: icon
|
|
804
|
+
}
|
|
805
|
+
)
|
|
806
|
+
),
|
|
807
|
+
/* @__PURE__ */ jsxRuntime.jsx(cosmosReact.Menu.Content, { width: "w-44", children: spec.menu.choices.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
808
|
+
cosmosReact.Menu.RadioItem,
|
|
499
809
|
{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
)
|
|
810
|
+
checked: item.id === choice,
|
|
811
|
+
"aria-keyshortcuts": item.shortcut && ariaShortcut(item.shortcut),
|
|
812
|
+
onSelect: () => item.run(editor),
|
|
813
|
+
children: labels[item.id]
|
|
814
|
+
},
|
|
815
|
+
item.id
|
|
816
|
+
)) })
|
|
507
817
|
] }, action);
|
|
508
818
|
}
|
|
509
|
-
if (
|
|
819
|
+
if (spec.form && formMode === "popover") {
|
|
820
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
821
|
+
cosmosReact.Popover,
|
|
822
|
+
{
|
|
823
|
+
open: form === spec.form && !inert,
|
|
824
|
+
onOpenChange: (open) => onFormChange(open ? spec.form ?? null : null),
|
|
825
|
+
children: [
|
|
826
|
+
tooltip(/* @__PURE__ */ jsxRuntime.jsx(cosmosReact.Popover.Trigger, { ...common, children: icon })),
|
|
827
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
828
|
+
cosmosReact.Popover.Content,
|
|
829
|
+
{
|
|
830
|
+
"aria-label": spec.form === "image" ? labels.imageDialog : labels.linkDialog,
|
|
831
|
+
position: "bottom",
|
|
832
|
+
width: spec.form === "image" ? "w-80" : "w-72",
|
|
833
|
+
padding: "p-3",
|
|
834
|
+
children: field(spec.form)
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
]
|
|
838
|
+
},
|
|
839
|
+
action
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
if (spec.form) {
|
|
843
|
+
const opens = spec.form;
|
|
510
844
|
return tooltip(
|
|
511
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
512
|
-
"button",
|
|
513
|
-
{
|
|
514
|
-
type: "button",
|
|
515
|
-
...common,
|
|
516
|
-
onClick: () => !disabled && onLinkOpenChange(true),
|
|
517
|
-
children: icon
|
|
518
|
-
}
|
|
519
|
-
)
|
|
845
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", ...common, onClick: () => !inert && onFormChange(opens), children: icon })
|
|
520
846
|
);
|
|
521
847
|
}
|
|
522
848
|
return tooltip(
|
|
@@ -534,23 +860,40 @@ var DEFAULTS = {
|
|
|
534
860
|
linkDialog: "Edit link",
|
|
535
861
|
linkApply: "Apply",
|
|
536
862
|
linkRemove: "Remove link",
|
|
537
|
-
linkInvalid: "Enter an http(s), mailto or relative link."
|
|
863
|
+
linkInvalid: "Enter an http(s), mailto or relative link.",
|
|
864
|
+
imageDialog: "Image details",
|
|
865
|
+
imageUrl: "Image URL",
|
|
866
|
+
imageAlt: "Description (alt text)",
|
|
867
|
+
imageDecorative: "Decorative image, no description",
|
|
868
|
+
imageApply: "Apply",
|
|
869
|
+
imageInvalid: "Enter an http(s) image address.",
|
|
870
|
+
imageAltRequired: "Describe the image, or mark it decorative."
|
|
538
871
|
};
|
|
872
|
+
var NAMED_DEFAULTS = Object.fromEntries([
|
|
873
|
+
...Object.entries(ACTION_SPECS).map(([action, spec]) => [action, spec.label]),
|
|
874
|
+
...Object.values(ACTION_SPECS).flatMap(
|
|
875
|
+
({ menu }) => (menu?.choices ?? []).map((choice) => [choice.id, choice.label])
|
|
876
|
+
)
|
|
877
|
+
]);
|
|
539
878
|
var resolveLabels = (labels = {}) => {
|
|
540
|
-
const
|
|
541
|
-
Object.keys(
|
|
542
|
-
action,
|
|
543
|
-
labels[action] ?? ACTION_SPECS[action].label
|
|
544
|
-
])
|
|
879
|
+
const named = Object.fromEntries(
|
|
880
|
+
Object.keys(NAMED_DEFAULTS).map((key2) => [key2, labels[key2] ?? NAMED_DEFAULTS[key2]])
|
|
545
881
|
);
|
|
546
882
|
return {
|
|
547
|
-
...
|
|
883
|
+
...named,
|
|
548
884
|
toolbar: labels.toolbar ?? DEFAULTS.toolbar,
|
|
549
885
|
linkUrl: labels.linkUrl ?? DEFAULTS.linkUrl,
|
|
550
886
|
linkDialog: labels.linkDialog ?? DEFAULTS.linkDialog,
|
|
551
887
|
linkApply: labels.linkApply ?? DEFAULTS.linkApply,
|
|
552
888
|
linkRemove: labels.linkRemove ?? DEFAULTS.linkRemove,
|
|
553
|
-
linkInvalid: labels.linkInvalid ?? DEFAULTS.linkInvalid
|
|
889
|
+
linkInvalid: labels.linkInvalid ?? DEFAULTS.linkInvalid,
|
|
890
|
+
imageDialog: labels.imageDialog ?? DEFAULTS.imageDialog,
|
|
891
|
+
imageUrl: labels.imageUrl ?? DEFAULTS.imageUrl,
|
|
892
|
+
imageAlt: labels.imageAlt ?? DEFAULTS.imageAlt,
|
|
893
|
+
imageDecorative: labels.imageDecorative ?? DEFAULTS.imageDecorative,
|
|
894
|
+
imageApply: labels.imageApply ?? DEFAULTS.imageApply,
|
|
895
|
+
imageInvalid: labels.imageInvalid ?? DEFAULTS.imageInvalid,
|
|
896
|
+
imageAltRequired: labels.imageAltRequired ?? DEFAULTS.imageAltRequired
|
|
554
897
|
};
|
|
555
898
|
};
|
|
556
899
|
var NO_PROPS = { format: "html", setCurrent: () => {
|
|
@@ -565,7 +908,7 @@ var createStore = (initial) => {
|
|
|
565
908
|
};
|
|
566
909
|
};
|
|
567
910
|
var EMPTY_DOC = { type: "doc", content: [{ type: "paragraph" }] };
|
|
568
|
-
var readContent = (editor, format) => format === "json" ? editor.getJSON() : editor.isEmpty ? "" : editor.getHTML();
|
|
911
|
+
var readContent = (editor, format) => format === "json" ? withoutDefaultAlignment(editor.getJSON()) : editor.isEmpty ? "" : editor.getHTML();
|
|
569
912
|
var countCharacters = (editor) => editor.storage.characterCount?.characters() ?? 0;
|
|
570
913
|
var sameContent = (a, b) => typeof a === "string" || typeof b === "string" ? a === b : JSON.stringify(a) === JSON.stringify(b);
|
|
571
914
|
var LinkShortcut = core.Extension.create({
|
|
@@ -575,12 +918,14 @@ var LinkShortcut = core.Extension.create({
|
|
|
575
918
|
return { "Mod-k": () => this.options.open() };
|
|
576
919
|
}
|
|
577
920
|
});
|
|
921
|
+
var BlockAlign = TextAlign__default.default.extend({ addKeyboardShortcuts: () => ({}) });
|
|
578
922
|
var CONTENT_CLASS = [
|
|
579
923
|
"min-w-0 outline-hidden break-words",
|
|
580
924
|
"[&>*+*]:mt-2",
|
|
581
925
|
// Each block follows the direction of what is typed in it, as a Textarea's paragraphs do
|
|
582
926
|
// (`FIELD_PLAINTEXT`); lists and quotes take a `dir` from `BlockDirection`.
|
|
583
|
-
"[&_p]:[unicode-bidi:plaintext] [&_h2]:[unicode-bidi:plaintext] [&_h3]:[unicode-bidi:plaintext]",
|
|
927
|
+
"[&_p]:[unicode-bidi:plaintext] [&_h1]:[unicode-bidi:plaintext] [&_h2]:[unicode-bidi:plaintext] [&_h3]:[unicode-bidi:plaintext]",
|
|
928
|
+
"[&_h1]:text-xl [&_h1]:leading-tight [&_h1]:font-semibold",
|
|
584
929
|
"[&_h2]:text-lg [&_h2]:leading-snug [&_h2]:font-semibold",
|
|
585
930
|
"[&_h3]:text-base [&_h3]:leading-snug [&_h3]:font-semibold",
|
|
586
931
|
"[&_ul]:list-disc [&_ul]:ps-5 [&_ol]:list-decimal [&_ol]:ps-5 [&_li>p]:my-0",
|
|
@@ -588,6 +933,13 @@ var CONTENT_CLASS = [
|
|
|
588
933
|
"dark:[&_blockquote]:border-gray-600 dark:[&_blockquote]:text-gray-300",
|
|
589
934
|
"[&_code]:rounded-sm [&_code]:bg-gray-100 [&_code]:px-1 [&_code]:font-mono [&_code]:text-[0.9em]",
|
|
590
935
|
"dark:[&_code]:bg-gray-800",
|
|
936
|
+
// A code block reads left to right in any page, as `CodeBlock` does; its `code` drops the pill.
|
|
937
|
+
"[&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-gray-100 [&_pre]:p-3 [&_pre]:font-mono [&_pre]:text-[0.9em] [&_pre]:leading-snug",
|
|
938
|
+
"dark:[&_pre]:bg-gray-800 [&_pre]:[direction:ltr] [&_pre]:text-start",
|
|
939
|
+
"[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-[1em]",
|
|
940
|
+
// An image is in the line: it never grows past it, and a selected one shows it.
|
|
941
|
+
"[&_img[src]]:inline-block [&_img[src]]:h-auto [&_img[src]]:max-w-full [&_img[src]]:align-middle",
|
|
942
|
+
"[&_img.ProseMirror-selectednode]:outline-brand-500 [&_img.ProseMirror-selectednode]:outline-2",
|
|
591
943
|
"[&_a]:text-brand-600 dark:[&_a]:text-brand-400 [&_a]:underline [&_a]:underline-offset-2",
|
|
592
944
|
"[&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:float-start [&_p.is-editor-empty:first-child]:before:h-0",
|
|
593
945
|
"[&_p.is-editor-empty:first-child]:before:text-gray-500 dark:[&_p.is-editor-empty:first-child]:before:text-gray-400",
|
|
@@ -632,7 +984,7 @@ function RichTextEditor({
|
|
|
632
984
|
onChange: (next) => onChange?.(next),
|
|
633
985
|
name: "RichTextEditor.value"
|
|
634
986
|
});
|
|
635
|
-
const [
|
|
987
|
+
const [form, setForm] = react.useState(null);
|
|
636
988
|
const [characters, setCharacters] = react.useState(0);
|
|
637
989
|
const [latest] = react.useState(() => createStore(NO_PROPS));
|
|
638
990
|
const [echoes] = react.useState(() => createStore([]));
|
|
@@ -643,7 +995,7 @@ function RichTextEditor({
|
|
|
643
995
|
setCurrent,
|
|
644
996
|
openLink: () => {
|
|
645
997
|
if (!hasLinkField || !editable) return false;
|
|
646
|
-
|
|
998
|
+
setForm("link");
|
|
647
999
|
return true;
|
|
648
1000
|
}
|
|
649
1001
|
});
|
|
@@ -658,11 +1010,9 @@ function RichTextEditor({
|
|
|
658
1010
|
content: current,
|
|
659
1011
|
extensions: [
|
|
660
1012
|
StarterKit__default.default.configure({
|
|
661
|
-
heading: { levels: [2, 3] },
|
|
1013
|
+
heading: { levels: [1, 2, 3] },
|
|
662
1014
|
// What the editor doesn't offer is out of the schema, so no paste or value brings it in.
|
|
663
|
-
codeBlock: false,
|
|
664
1015
|
horizontalRule: false,
|
|
665
|
-
underline: false,
|
|
666
1016
|
trailingNode: false,
|
|
667
1017
|
link: {
|
|
668
1018
|
openOnClick: false,
|
|
@@ -671,6 +1021,10 @@ function RichTextEditor({
|
|
|
671
1021
|
isAllowedUri: (url) => isSafeLinkUrl(url)
|
|
672
1022
|
}
|
|
673
1023
|
}),
|
|
1024
|
+
// Center and end only, as the sides of the line: a pasted `left`, `right` or `justify` is
|
|
1025
|
+
// dropped, and the start is a block with nothing set.
|
|
1026
|
+
BlockAlign.configure({ types: ["paragraph", "heading"], alignments: ["center", "end"] }),
|
|
1027
|
+
RichTextImage,
|
|
674
1028
|
extensions.Placeholder.configure({ placeholder: () => latest.get().placeholder ?? "" }),
|
|
675
1029
|
...maxLength !== void 0 ? [extensions.CharacterCount.configure({ limit: maxLength })] : [],
|
|
676
1030
|
LinkShortcut.configure({ open: () => latest.get().openLink() }),
|
|
@@ -790,9 +1144,9 @@ function RichTextEditor({
|
|
|
790
1144
|
actions,
|
|
791
1145
|
labels,
|
|
792
1146
|
disabled,
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1147
|
+
form,
|
|
1148
|
+
onFormChange: setForm,
|
|
1149
|
+
formMode: "popover",
|
|
796
1150
|
className: "border-b border-gray-200 p-1 dark:border-gray-700"
|
|
797
1151
|
}
|
|
798
1152
|
),
|
|
@@ -806,7 +1160,7 @@ function RichTextEditor({
|
|
|
806
1160
|
strategy: "fixed",
|
|
807
1161
|
placement: "top",
|
|
808
1162
|
offset: 8,
|
|
809
|
-
onHide: () =>
|
|
1163
|
+
onHide: () => setForm(null)
|
|
810
1164
|
},
|
|
811
1165
|
className: "z-[9999]",
|
|
812
1166
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -817,11 +1171,11 @@ function RichTextEditor({
|
|
|
817
1171
|
actions,
|
|
818
1172
|
labels,
|
|
819
1173
|
disabled,
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1174
|
+
form,
|
|
1175
|
+
onFormChange: setForm,
|
|
1176
|
+
formMode: "inline",
|
|
823
1177
|
onEscape: () => {
|
|
824
|
-
|
|
1178
|
+
setForm(null);
|
|
825
1179
|
editor.commands.focus();
|
|
826
1180
|
},
|
|
827
1181
|
className: "rounded-lg border border-gray-200 bg-white p-1 shadow-lg dark:border-gray-700 dark:bg-gray-900"
|
|
@@ -867,6 +1221,7 @@ function RichTextEditor({
|
|
|
867
1221
|
{
|
|
868
1222
|
type: "hidden",
|
|
869
1223
|
name,
|
|
1224
|
+
disabled,
|
|
870
1225
|
value: typeof current === "string" ? current : JSON.stringify(current)
|
|
871
1226
|
}
|
|
872
1227
|
)
|