@mocanvas/mocanvas 1.0.0 → 4.0.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/ARCHITECTURE.md +421 -0
- package/BENCHMARK.md +519 -0
- package/CLEAN_ROOM.md +50 -0
- package/COMPAT.md +282 -0
- package/CUSTOM_SHAPES.md +880 -0
- package/LICENSE +110 -16
- package/MIGRATION.md +807 -0
- package/README.md +122 -6
- package/UI.md +256 -0
- package/dist/chunk-DMCI6V5Y.js +3370 -0
- package/dist/chunk-DMCI6V5Y.js.map +1 -0
- package/dist/chunk-GYF6AY4N.js +54 -0
- package/dist/chunk-GYF6AY4N.js.map +1 -0
- package/dist/chunk-OCYJAMXT.js +1117 -0
- package/dist/chunk-OCYJAMXT.js.map +1 -0
- package/dist/chunk-OMUOD53T.js +370 -0
- package/dist/chunk-OMUOD53T.js.map +1 -0
- package/dist/chunk-QDUQZXE4.js +1340 -0
- package/dist/chunk-QDUQZXE4.js.map +1 -0
- package/dist/chunk-TWCVRVK2.js +296 -0
- package/dist/chunk-TWCVRVK2.js.map +1 -0
- package/dist/export-VQGOVONC.js +5 -0
- package/dist/export-VQGOVONC.js.map +1 -0
- package/dist/file-GEHTVI2G.js +3 -0
- package/dist/file-GEHTVI2G.js.map +1 -0
- package/dist/index.d.ts +9593 -1659
- package/dist/index.js +9005 -6114
- package/dist/index.js.map +1 -1
- package/dist/panel-shortcuts-R2PDTDUN.js +4 -0
- package/dist/panel-shortcuts-R2PDTDUN.js.map +1 -0
- package/dist/{ui-62NV5N7K.css → ui-4C5V5GYT.css} +385 -0
- package/dist/urlContent-GC44EU52.js +4 -0
- package/dist/urlContent-GC44EU52.js.map +1 -0
- package/package.json +17 -16
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import { T, ImageShapeCrop, ARROWHEAD_KINDS, ARROW_SHAPE_KINDS, LINE_SPLINE_KINDS, assetIdValidator, createBuiltInShapePropsMigrationIds, createBindingPropsMigrationSequence, createBuiltInBindingPropsMigrationIds, createShapePropsMigrationSequence, DefaultFontStyle, DefaultSizeStyle, DefaultDashStyle, DefaultFillStyle, DefaultLabelColorStyle, DefaultColorStyle, DefaultVerticalAlignStyle, DefaultHorizontalAlignStyle, GeoShapeGeoStyle, richTextToPlainText, createShapeId, BaseBoxShapeUtil, Rectangle2d, getDefaultDisplayValues } from '@mocanvas/editor';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/text/tiptap-extensions.ts
|
|
5
|
+
var ZERO_WIDTH_SPACE = "\u200B";
|
|
6
|
+
function level(node) {
|
|
7
|
+
const value = node.attrs?.["level"];
|
|
8
|
+
return typeof value === "number" && value >= 1 && value <= 6 ? Math.floor(value) : 1;
|
|
9
|
+
}
|
|
10
|
+
var RICH_TEXT_NODES = [
|
|
11
|
+
{ name: "paragraph", kind: "node", tag: "p", emptyHtml: ZERO_WIDTH_SPACE },
|
|
12
|
+
{ name: "heading", kind: "node", tag: "h1", tagFor: (node) => `h${level(node)}`, emptyHtml: ZERO_WIDTH_SPACE },
|
|
13
|
+
{ name: "bulletList", kind: "node", tag: "ul" },
|
|
14
|
+
{ name: "orderedList", kind: "node", tag: "ol" },
|
|
15
|
+
{ name: "listItem", kind: "node", tag: "li", emptyHtml: ZERO_WIDTH_SPACE },
|
|
16
|
+
{ name: "blockquote", kind: "node", tag: "blockquote" },
|
|
17
|
+
{ name: "codeBlock", kind: "node", tag: "pre" },
|
|
18
|
+
{ name: "hardBreak", kind: "node", tag: "br", void: true },
|
|
19
|
+
{ name: "horizontalRule", kind: "node", tag: "hr", void: true }
|
|
20
|
+
];
|
|
21
|
+
var RICH_TEXT_MARKS = [
|
|
22
|
+
{ name: "bold", kind: "mark", tag: "strong" },
|
|
23
|
+
{ name: "italic", kind: "mark", tag: "em" },
|
|
24
|
+
{ name: "underline", kind: "mark", tag: "u" },
|
|
25
|
+
{ name: "strike", kind: "mark", tag: "s" },
|
|
26
|
+
{ name: "code", kind: "mark", tag: "code" },
|
|
27
|
+
{ name: "highlight", kind: "mark", tag: "mark" },
|
|
28
|
+
{
|
|
29
|
+
name: "link",
|
|
30
|
+
kind: "mark",
|
|
31
|
+
tag: "a",
|
|
32
|
+
attributesFor: (mark) => {
|
|
33
|
+
const href = mark.attrs?.["href"];
|
|
34
|
+
if (typeof href !== "string") return null;
|
|
35
|
+
const trimmed = href.trim();
|
|
36
|
+
return /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i.test(trimmed) ? { href: trimmed, rel: "noopener noreferrer" } : null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
var tipTapDefaultExtensions = [...RICH_TEXT_NODES, ...RICH_TEXT_MARKS];
|
|
41
|
+
var editorFactory = null;
|
|
42
|
+
function registerRichTextEditorFactory(factory) {
|
|
43
|
+
editorFactory = factory;
|
|
44
|
+
return () => {
|
|
45
|
+
if (editorFactory === factory) editorFactory = null;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function getRichTextEditorFactory() {
|
|
49
|
+
return editorFactory;
|
|
50
|
+
}
|
|
51
|
+
async function loadTipTapStarterExtensions() {
|
|
52
|
+
const moduleId = "@tiptap/starter-kit";
|
|
53
|
+
let loaded;
|
|
54
|
+
try {
|
|
55
|
+
loaded = await import(
|
|
56
|
+
/* @vite-ignore */
|
|
57
|
+
/* webpackIgnore: true */
|
|
58
|
+
moduleId
|
|
59
|
+
);
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
throw new Error("mocanvas: `@tiptap/starter-kit` is an optional peer dependency and is not installed. Run `npm install @tiptap/starter-kit@^3 @tiptap/core@^3 @tiptap/pm@^3`, or use `tipTapDefaultExtensions` instead.", { cause });
|
|
62
|
+
}
|
|
63
|
+
const starterKit = loaded.default ?? loaded;
|
|
64
|
+
return [starterKit];
|
|
65
|
+
}
|
|
66
|
+
function toRichText(text) {
|
|
67
|
+
const source = typeof text === "string" ? text : "";
|
|
68
|
+
return {
|
|
69
|
+
type: "doc",
|
|
70
|
+
content: source.split("\n").map((line) => line.length === 0 ? { type: "paragraph" } : { type: "paragraph", content: [{ type: "text", text: line }] })
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isRichText(value) {
|
|
74
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
75
|
+
const doc = value;
|
|
76
|
+
return doc.type === "doc" && (doc.content === void 0 || Array.isArray(doc.content));
|
|
77
|
+
}
|
|
78
|
+
function richTextToText(source) {
|
|
79
|
+
if (source === null || source === void 0) return "";
|
|
80
|
+
if (typeof source === "string") return source;
|
|
81
|
+
return richTextToPlainText(source);
|
|
82
|
+
}
|
|
83
|
+
function asRichText(source) {
|
|
84
|
+
if (isRichText(source)) return source;
|
|
85
|
+
return toRichText(typeof source === "string" ? source : "");
|
|
86
|
+
}
|
|
87
|
+
function richTextEquals(a, b) {
|
|
88
|
+
if (a === b) return true;
|
|
89
|
+
return JSON.stringify(asRichText(a)) === JSON.stringify(asRichText(b));
|
|
90
|
+
}
|
|
91
|
+
function applyPlainTextToRichText(previous, nextText) {
|
|
92
|
+
const doc = asRichText(previous);
|
|
93
|
+
if (richTextToText(doc) === nextText) return doc;
|
|
94
|
+
return toRichText(nextText);
|
|
95
|
+
}
|
|
96
|
+
var SAFE_URL = /^(?:https?:|mailto:|tel:|#|\/|\.{1,2}\/)/i;
|
|
97
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
98
|
+
function escapeHtml(value) {
|
|
99
|
+
return value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
100
|
+
}
|
|
101
|
+
function resolve(extensions) {
|
|
102
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
103
|
+
const marks = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const extension of extensions ?? [...RICH_TEXT_NODES, ...RICH_TEXT_MARKS]) {
|
|
105
|
+
const kind = extension.kind;
|
|
106
|
+
if (kind === "node") nodes.set(extension.name, extension);
|
|
107
|
+
else if (kind === "mark") marks.set(extension.name, extension);
|
|
108
|
+
}
|
|
109
|
+
if (nodes.size === 0) for (const node of RICH_TEXT_NODES) nodes.set(node.name, node);
|
|
110
|
+
if (marks.size === 0) for (const mark of RICH_TEXT_MARKS) marks.set(mark.name, mark);
|
|
111
|
+
return { nodes, marks };
|
|
112
|
+
}
|
|
113
|
+
function richTextToHtml(source, options = {}) {
|
|
114
|
+
const { nodes, marks } = resolve(options.extensions);
|
|
115
|
+
const doc = asRichText(source);
|
|
116
|
+
return (doc.content ?? []).map((node) => nodeToHtml(node, nodes, marks, options.unknownTypes)).join("");
|
|
117
|
+
}
|
|
118
|
+
function nodeToHtml(node, nodes, marks, unknown) {
|
|
119
|
+
if (typeof node !== "object" || node === null) return "";
|
|
120
|
+
if (node.type === "text") return textNodeToHtml(node, marks, unknown);
|
|
121
|
+
const extension = nodes.get(node.type);
|
|
122
|
+
if (!extension) {
|
|
123
|
+
unknown?.add(node.type);
|
|
124
|
+
return (node.content ?? []).map((child) => nodeToHtml(child, nodes, marks, unknown)).join("");
|
|
125
|
+
}
|
|
126
|
+
const tag = extension.tagFor ? extension.tagFor(node) : extension.tag;
|
|
127
|
+
if (extension.void) return tag === null ? "" : `<${tag}>`;
|
|
128
|
+
const inner = (node.content ?? []).map((child) => nodeToHtml(child, nodes, marks, unknown)).join("");
|
|
129
|
+
if (tag === null) return inner;
|
|
130
|
+
return `<${tag}>${inner.length === 0 && extension.emptyHtml !== void 0 ? extension.emptyHtml : inner}</${tag}>`;
|
|
131
|
+
}
|
|
132
|
+
function textNodeToHtml(node, marks, unknown) {
|
|
133
|
+
let html = escapeHtml(node.text ?? "");
|
|
134
|
+
for (const mark of node.marks ?? []) {
|
|
135
|
+
const extension = marks.get(mark.type);
|
|
136
|
+
if (!extension) {
|
|
137
|
+
unknown?.add(mark.type);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const attributes = extension.attributesFor?.(mark) ?? null;
|
|
141
|
+
if (attributes === null && extension.attributesFor) continue;
|
|
142
|
+
const rendered = attributes === null ? "" : Object.entries(attributes).map(([k, v]) => ` ${k}="${escapeHtml(v)}"`).join("");
|
|
143
|
+
html = `<${extension.tag}${rendered}>${html}</${extension.tag}>`;
|
|
144
|
+
}
|
|
145
|
+
return html;
|
|
146
|
+
}
|
|
147
|
+
function safeHref(value) {
|
|
148
|
+
if (typeof value !== "string") return null;
|
|
149
|
+
const trimmed = value.trim();
|
|
150
|
+
return SAFE_URL.test(trimmed) ? trimmed : null;
|
|
151
|
+
}
|
|
152
|
+
var PLAIN_STYLE = { bold: false, italic: false, underline: false, strike: false, code: false };
|
|
153
|
+
var BOLD_MARKS = /* @__PURE__ */ new Set(["bold", "strong"]);
|
|
154
|
+
var ITALIC_MARKS = /* @__PURE__ */ new Set(["italic", "em"]);
|
|
155
|
+
var UNDERLINE_MARKS = /* @__PURE__ */ new Set(["underline"]);
|
|
156
|
+
var STRIKE_MARKS = /* @__PURE__ */ new Set(["strike", "strikethrough", "s", "del"]);
|
|
157
|
+
var CODE_MARKS = /* @__PURE__ */ new Set(["code"]);
|
|
158
|
+
function styleOf(marks) {
|
|
159
|
+
const style = { ...PLAIN_STYLE };
|
|
160
|
+
for (const mark of marks ?? []) {
|
|
161
|
+
if (BOLD_MARKS.has(mark.type)) style.bold = true;
|
|
162
|
+
else if (ITALIC_MARKS.has(mark.type)) style.italic = true;
|
|
163
|
+
else if (UNDERLINE_MARKS.has(mark.type)) style.underline = true;
|
|
164
|
+
else if (STRIKE_MARKS.has(mark.type)) style.strike = true;
|
|
165
|
+
else if (CODE_MARKS.has(mark.type)) style.code = true;
|
|
166
|
+
else if (mark.type === "link") {
|
|
167
|
+
const href = safeHref(mark.attrs?.["href"]);
|
|
168
|
+
if (href !== null) style.href = href;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return style;
|
|
172
|
+
}
|
|
173
|
+
function richTextToBlocks(source) {
|
|
174
|
+
if (typeof source === "string") return source.split("\n").map((text) => ({ runs: text.length === 0 ? [] : [{ text, ...PLAIN_STYLE }] }));
|
|
175
|
+
const doc = asRichText(source);
|
|
176
|
+
const blocks = [];
|
|
177
|
+
for (const node of doc.content ?? []) collectBlocks(node, blocks);
|
|
178
|
+
if (blocks.length === 0) blocks.push({ runs: [] });
|
|
179
|
+
return blocks;
|
|
180
|
+
}
|
|
181
|
+
function collectBlocks(node, out) {
|
|
182
|
+
if (typeof node !== "object" || node === null) return;
|
|
183
|
+
if (node.type === "text" || node.type === "hardBreak") {
|
|
184
|
+
appendInline(node, out);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const children = node.content ?? [];
|
|
188
|
+
if (children.some((child) => isBlockNode(child))) {
|
|
189
|
+
for (const child of children) collectBlocks(child, out);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
out.push({ runs: [] });
|
|
193
|
+
for (const child of children) appendInline(child, out);
|
|
194
|
+
}
|
|
195
|
+
var BLOCK_NODE_TYPES = /* @__PURE__ */ new Set(["paragraph", "heading", "bulletList", "orderedList", "listItem", "blockquote", "codeBlock", "horizontalRule", "taskList", "taskItem", "table", "tableRow", "tableCell", "tableHeader"]);
|
|
196
|
+
function isBlockNode(node) {
|
|
197
|
+
if (typeof node !== "object" || node === null) return false;
|
|
198
|
+
if (node.type === "text" || node.type === "hardBreak") return false;
|
|
199
|
+
if (Array.isArray(node.marks) && node.marks.length > 0) return false;
|
|
200
|
+
return BLOCK_NODE_TYPES.has(node.type) || Array.isArray(node.content);
|
|
201
|
+
}
|
|
202
|
+
function appendInline(node, out, inherited) {
|
|
203
|
+
if (typeof node !== "object" || node === null) return;
|
|
204
|
+
if (out.length === 0) out.push({ runs: [] });
|
|
205
|
+
if (node.type === "hardBreak") {
|
|
206
|
+
out.push({ runs: [] });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const style = mergeStyle(inherited, styleOf(node.marks));
|
|
210
|
+
if (node.type === "text") {
|
|
211
|
+
const text = node.text ?? "";
|
|
212
|
+
if (text.length > 0) out[out.length - 1].runs.push({ text, ...style });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const child of node.content ?? []) appendInline(child, out, style);
|
|
216
|
+
}
|
|
217
|
+
function mergeStyle(base, next) {
|
|
218
|
+
if (!base) return next;
|
|
219
|
+
return {
|
|
220
|
+
bold: base.bold || next.bold,
|
|
221
|
+
italic: base.italic || next.italic,
|
|
222
|
+
underline: base.underline || next.underline,
|
|
223
|
+
strike: base.strike || next.strike,
|
|
224
|
+
code: base.code || next.code,
|
|
225
|
+
...next.href ?? base.href ? { href: next.href ?? base.href } : {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function propsOf(shape) {
|
|
229
|
+
const props = shape.props;
|
|
230
|
+
return typeof props === "object" && props !== null && !Array.isArray(props) ? props : {};
|
|
231
|
+
}
|
|
232
|
+
function readString(props, key, fallback) {
|
|
233
|
+
const value = props?.[key];
|
|
234
|
+
return typeof value === "string" ? value : fallback;
|
|
235
|
+
}
|
|
236
|
+
function readEnum(props, key, allowed, fallback) {
|
|
237
|
+
const value = props?.[key];
|
|
238
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
239
|
+
}
|
|
240
|
+
function readStyle(props, key, style, fallback = style.defaultValue) {
|
|
241
|
+
return readEnum(props, key, style.values, fallback);
|
|
242
|
+
}
|
|
243
|
+
function readNumber(props, key, fallback) {
|
|
244
|
+
const value = props?.[key];
|
|
245
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
246
|
+
}
|
|
247
|
+
function readBoolean(props, key, fallback) {
|
|
248
|
+
const value = props?.[key];
|
|
249
|
+
return typeof value === "boolean" ? value : fallback;
|
|
250
|
+
}
|
|
251
|
+
function readText(props, key = "text") {
|
|
252
|
+
if (key !== "text") return readString(props, key, "");
|
|
253
|
+
return richTextToPlainText(readRichText(props));
|
|
254
|
+
}
|
|
255
|
+
function readRichText(props, key = "richText") {
|
|
256
|
+
const value = props?.[key];
|
|
257
|
+
const stored = isRichText(value) ? value : null;
|
|
258
|
+
if (stored !== null && richTextToPlainText(stored).length > 0) return stored;
|
|
259
|
+
const text = props?.["text"];
|
|
260
|
+
if (typeof text === "string" && text.length > 0) return toRichText(text);
|
|
261
|
+
return stored ?? toRichText("");
|
|
262
|
+
}
|
|
263
|
+
function readPoint(props, key, fallback) {
|
|
264
|
+
const value = props?.[key];
|
|
265
|
+
if (typeof value !== "object" || value === null) return fallback;
|
|
266
|
+
return { x: readNumber(value, "x", fallback.x), y: readNumber(value, "y", fallback.y) };
|
|
267
|
+
}
|
|
268
|
+
function readArray(props, key) {
|
|
269
|
+
const value = props?.[key];
|
|
270
|
+
return Array.isArray(value) ? value : [];
|
|
271
|
+
}
|
|
272
|
+
function readRecord(props, key) {
|
|
273
|
+
const value = props?.[key];
|
|
274
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/shapes/indicator-paths.ts
|
|
278
|
+
function canBuildPath() {
|
|
279
|
+
return typeof Path2D !== "undefined";
|
|
280
|
+
}
|
|
281
|
+
function rectPath(w, h, radius = 0) {
|
|
282
|
+
const path = new Path2D();
|
|
283
|
+
if (radius > 0 && typeof path.roundRect === "function") {
|
|
284
|
+
path.roundRect(0, 0, w, h, radius);
|
|
285
|
+
} else {
|
|
286
|
+
path.rect(0, 0, w, h);
|
|
287
|
+
}
|
|
288
|
+
return path;
|
|
289
|
+
}
|
|
290
|
+
function boxPath(box) {
|
|
291
|
+
const path = new Path2D();
|
|
292
|
+
path.rect(box.x, box.y, box.w, box.h);
|
|
293
|
+
return path;
|
|
294
|
+
}
|
|
295
|
+
function svgPath(d) {
|
|
296
|
+
return new Path2D(d);
|
|
297
|
+
}
|
|
298
|
+
function polylinePath(points, close = false) {
|
|
299
|
+
const path = new Path2D();
|
|
300
|
+
const first = points[0];
|
|
301
|
+
if (!first) return path;
|
|
302
|
+
path.moveTo(first.x, first.y);
|
|
303
|
+
for (let i = 1; i < points.length; i++) {
|
|
304
|
+
const p = points[i];
|
|
305
|
+
path.lineTo(p.x, p.y);
|
|
306
|
+
}
|
|
307
|
+
if (close) path.closePath();
|
|
308
|
+
return path;
|
|
309
|
+
}
|
|
310
|
+
var richTextValidator = T.jsonValue.refine((value) => {
|
|
311
|
+
if (!isRichText(value)) throw new Error(`Expected a rich text document, got ${JSON.stringify(value)}`);
|
|
312
|
+
return { type: "doc", content: value.content ?? [] };
|
|
313
|
+
});
|
|
314
|
+
function styleValue(style) {
|
|
315
|
+
return T.unknown.refine((value) => style.validate(value));
|
|
316
|
+
}
|
|
317
|
+
var imageCropValidator = ImageShapeCrop.refine(
|
|
318
|
+
(crop) => crop.isCircle === void 0 ? { topLeft: crop.topLeft, bottomRight: crop.bottomRight } : { topLeft: crop.topLeft, bottomRight: crop.bottomRight, isCircle: crop.isCircle }
|
|
319
|
+
);
|
|
320
|
+
var pointValidator = T.object({ x: T.number, y: T.number });
|
|
321
|
+
var drawPointValidator = T.object({ x: T.number, y: T.number, z: T.number.optional() });
|
|
322
|
+
var drawSegmentValidator = T.object({
|
|
323
|
+
type: T.literalEnum("free", "straight"),
|
|
324
|
+
points: T.arrayOf(drawPointValidator)
|
|
325
|
+
});
|
|
326
|
+
var derivedTextValidator = T.string.optional();
|
|
327
|
+
var geoShapeProps = {
|
|
328
|
+
geo: GeoShapeGeoStyle,
|
|
329
|
+
w: T.nonZeroNumber,
|
|
330
|
+
h: T.nonZeroNumber,
|
|
331
|
+
color: DefaultColorStyle,
|
|
332
|
+
labelColor: DefaultLabelColorStyle,
|
|
333
|
+
fill: DefaultFillStyle,
|
|
334
|
+
dash: DefaultDashStyle,
|
|
335
|
+
size: DefaultSizeStyle,
|
|
336
|
+
font: DefaultFontStyle,
|
|
337
|
+
align: DefaultHorizontalAlignStyle,
|
|
338
|
+
verticalAlign: DefaultVerticalAlignStyle,
|
|
339
|
+
growY: T.positiveNumber,
|
|
340
|
+
url: T.linkUrl,
|
|
341
|
+
richText: richTextValidator,
|
|
342
|
+
text: derivedTextValidator,
|
|
343
|
+
scale: T.nonZeroNumber,
|
|
344
|
+
flipX: T.boolean,
|
|
345
|
+
flipY: T.boolean
|
|
346
|
+
};
|
|
347
|
+
var arrowShapeProps = {
|
|
348
|
+
kind: T.literalEnum(...ARROW_SHAPE_KINDS),
|
|
349
|
+
start: pointValidator,
|
|
350
|
+
end: pointValidator,
|
|
351
|
+
bend: T.number,
|
|
352
|
+
elbowMidPoint: T.number,
|
|
353
|
+
color: DefaultColorStyle,
|
|
354
|
+
labelColor: DefaultLabelColorStyle,
|
|
355
|
+
fill: DefaultFillStyle,
|
|
356
|
+
dash: DefaultDashStyle,
|
|
357
|
+
size: DefaultSizeStyle,
|
|
358
|
+
arrowheadStart: T.literalEnum(...ARROWHEAD_KINDS),
|
|
359
|
+
arrowheadEnd: T.literalEnum(...ARROWHEAD_KINDS),
|
|
360
|
+
font: DefaultFontStyle,
|
|
361
|
+
richText: richTextValidator,
|
|
362
|
+
text: derivedTextValidator,
|
|
363
|
+
labelPosition: T.number,
|
|
364
|
+
scale: T.nonZeroNumber
|
|
365
|
+
};
|
|
366
|
+
var drawShapeProps = {
|
|
367
|
+
segments: T.arrayOf(drawSegmentValidator),
|
|
368
|
+
color: DefaultColorStyle,
|
|
369
|
+
fill: DefaultFillStyle,
|
|
370
|
+
dash: DefaultDashStyle,
|
|
371
|
+
size: DefaultSizeStyle,
|
|
372
|
+
isComplete: T.boolean,
|
|
373
|
+
isClosed: T.boolean,
|
|
374
|
+
isPen: T.boolean,
|
|
375
|
+
scale: T.nonZeroNumber
|
|
376
|
+
};
|
|
377
|
+
var highlightShapeProps = {
|
|
378
|
+
segments: T.arrayOf(drawSegmentValidator),
|
|
379
|
+
color: DefaultColorStyle,
|
|
380
|
+
size: DefaultSizeStyle,
|
|
381
|
+
isComplete: T.boolean,
|
|
382
|
+
isPen: T.boolean,
|
|
383
|
+
scale: T.nonZeroNumber
|
|
384
|
+
};
|
|
385
|
+
var lineShapeProps = {
|
|
386
|
+
color: DefaultColorStyle,
|
|
387
|
+
dash: DefaultDashStyle,
|
|
388
|
+
size: DefaultSizeStyle,
|
|
389
|
+
spline: T.literalEnum(...LINE_SPLINE_KINDS),
|
|
390
|
+
points: T.dict(T.string, T.object({ id: T.string, index: T.string, x: T.number, y: T.number })),
|
|
391
|
+
scale: T.nonZeroNumber
|
|
392
|
+
};
|
|
393
|
+
var textShapeProps = {
|
|
394
|
+
color: DefaultColorStyle,
|
|
395
|
+
size: DefaultSizeStyle,
|
|
396
|
+
font: DefaultFontStyle,
|
|
397
|
+
textAlign: DefaultHorizontalAlignStyle,
|
|
398
|
+
w: T.nonZeroNumber,
|
|
399
|
+
richText: richTextValidator,
|
|
400
|
+
text: derivedTextValidator,
|
|
401
|
+
scale: T.nonZeroNumber,
|
|
402
|
+
autoSize: T.boolean
|
|
403
|
+
};
|
|
404
|
+
var noteShapeProps = {
|
|
405
|
+
color: DefaultColorStyle,
|
|
406
|
+
labelColor: DefaultLabelColorStyle,
|
|
407
|
+
size: DefaultSizeStyle,
|
|
408
|
+
font: DefaultFontStyle,
|
|
409
|
+
fontSizeAdjustment: T.positiveNumber,
|
|
410
|
+
align: DefaultHorizontalAlignStyle,
|
|
411
|
+
verticalAlign: DefaultVerticalAlignStyle,
|
|
412
|
+
growY: T.positiveNumber,
|
|
413
|
+
url: T.linkUrl,
|
|
414
|
+
richText: richTextValidator,
|
|
415
|
+
text: derivedTextValidator,
|
|
416
|
+
// Who first edited the label by hand, or `null` while nobody has. Absent on
|
|
417
|
+
// every note written before attribution existed, so optional as well.
|
|
418
|
+
textFirstEditedBy: T.string.nullable().optional(),
|
|
419
|
+
scale: T.nonZeroNumber
|
|
420
|
+
};
|
|
421
|
+
var frameShapeProps = {
|
|
422
|
+
w: T.nonZeroNumber,
|
|
423
|
+
h: T.nonZeroNumber,
|
|
424
|
+
name: T.string,
|
|
425
|
+
// A frame's colour is per frame: a frame is chrome around other people's
|
|
426
|
+
// shapes, and recolouring a mixed selection should not repaint the frames it
|
|
427
|
+
// happens to contain. Hence a style *value*, not a style.
|
|
428
|
+
color: styleValue(DefaultColorStyle).optional()
|
|
429
|
+
};
|
|
430
|
+
var groupShapeProps = {};
|
|
431
|
+
var imageShapeProps = {
|
|
432
|
+
w: T.nonZeroNumber,
|
|
433
|
+
h: T.nonZeroNumber,
|
|
434
|
+
assetId: assetIdValidator.nullable(),
|
|
435
|
+
playing: T.boolean,
|
|
436
|
+
url: T.linkUrl,
|
|
437
|
+
crop: imageCropValidator.nullable(),
|
|
438
|
+
flipX: T.boolean,
|
|
439
|
+
flipY: T.boolean,
|
|
440
|
+
altText: T.string
|
|
441
|
+
};
|
|
442
|
+
var videoShapeProps = {
|
|
443
|
+
w: T.nonZeroNumber,
|
|
444
|
+
h: T.nonZeroNumber,
|
|
445
|
+
assetId: assetIdValidator.nullable(),
|
|
446
|
+
time: T.number,
|
|
447
|
+
playing: T.boolean,
|
|
448
|
+
url: T.linkUrl,
|
|
449
|
+
altText: T.string
|
|
450
|
+
};
|
|
451
|
+
var bookmarkShapeProps = {
|
|
452
|
+
w: T.nonZeroNumber,
|
|
453
|
+
h: T.nonZeroNumber,
|
|
454
|
+
assetId: assetIdValidator.nullable(),
|
|
455
|
+
url: T.linkUrl
|
|
456
|
+
};
|
|
457
|
+
var embedShapeProps = {
|
|
458
|
+
w: T.nonZeroNumber,
|
|
459
|
+
h: T.nonZeroNumber,
|
|
460
|
+
url: T.linkUrl
|
|
461
|
+
};
|
|
462
|
+
var arrowBindingProps = {
|
|
463
|
+
terminal: T.literalEnum("start", "end"),
|
|
464
|
+
normalizedAnchor: pointValidator,
|
|
465
|
+
isExact: T.boolean,
|
|
466
|
+
isPrecise: T.boolean
|
|
467
|
+
};
|
|
468
|
+
function backfillMigration(type, defaults, options = {}) {
|
|
469
|
+
const versions = createBuiltInShapePropsMigrationIds(type, { BackfillMissingProps: 1 });
|
|
470
|
+
return createShapePropsMigrationSequence({
|
|
471
|
+
sequence: [
|
|
472
|
+
{
|
|
473
|
+
id: versions.BackfillMissingProps,
|
|
474
|
+
up(props) {
|
|
475
|
+
if (options.richTextFromText && props["richText"] === void 0) {
|
|
476
|
+
props["richText"] = toRichText(typeof props["text"] === "string" ? props["text"] : "");
|
|
477
|
+
}
|
|
478
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
479
|
+
if (props[key] === void 0) props[key] = structuredClone(value);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
down() {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
var arrowShapeVersions = createBuiltInShapePropsMigrationIds("arrow", {
|
|
489
|
+
BackfillMissingProps: 1
|
|
490
|
+
});
|
|
491
|
+
var arrowShapeMigrations = backfillMigration(
|
|
492
|
+
"arrow",
|
|
493
|
+
{ kind: "arc", elbowMidPoint: 0.5, labelPosition: 0.5, scale: 1 },
|
|
494
|
+
{ richTextFromText: true }
|
|
495
|
+
);
|
|
496
|
+
var drawShapeMigrations = backfillMigration("draw", {
|
|
497
|
+
isComplete: true,
|
|
498
|
+
isClosed: false,
|
|
499
|
+
isPen: false,
|
|
500
|
+
scale: 1
|
|
501
|
+
});
|
|
502
|
+
var highlightShapeMigrations = backfillMigration("highlight", {
|
|
503
|
+
isComplete: true,
|
|
504
|
+
isPen: false,
|
|
505
|
+
scale: 1
|
|
506
|
+
});
|
|
507
|
+
var lineShapeMigrations = backfillMigration("line", { spline: "line", scale: 1 });
|
|
508
|
+
var textShapeMigrations = backfillMigration("text", { autoSize: true, scale: 1 }, { richTextFromText: true });
|
|
509
|
+
var noteShapeMigrations = backfillMigration(
|
|
510
|
+
"note",
|
|
511
|
+
{ fontSizeAdjustment: 0, growY: 0, url: "", scale: 1 },
|
|
512
|
+
{ richTextFromText: true }
|
|
513
|
+
);
|
|
514
|
+
var frameShapeMigrations = backfillMigration("frame", { name: "" });
|
|
515
|
+
var groupShapeMigrations = backfillMigration("group", {});
|
|
516
|
+
var imageShapeMigrations = backfillMigration("image", {
|
|
517
|
+
assetId: null,
|
|
518
|
+
playing: true,
|
|
519
|
+
url: "",
|
|
520
|
+
crop: null,
|
|
521
|
+
flipX: false,
|
|
522
|
+
flipY: false,
|
|
523
|
+
altText: ""
|
|
524
|
+
});
|
|
525
|
+
var videoShapeMigrations = backfillMigration("video", {
|
|
526
|
+
assetId: null,
|
|
527
|
+
time: 0,
|
|
528
|
+
playing: true,
|
|
529
|
+
url: "",
|
|
530
|
+
altText: ""
|
|
531
|
+
});
|
|
532
|
+
var bookmarkShapeMigrations = backfillMigration("bookmark", { assetId: null, url: "" });
|
|
533
|
+
var embedShapeMigrations = backfillMigration("embed", { url: "" });
|
|
534
|
+
var arrowBindingMigrations = createBindingPropsMigrationSequence({
|
|
535
|
+
sequence: [
|
|
536
|
+
{
|
|
537
|
+
id: createBuiltInBindingPropsMigrationIds("arrow", { Initial: 1 }).Initial,
|
|
538
|
+
up() {
|
|
539
|
+
},
|
|
540
|
+
down() {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
]
|
|
544
|
+
});
|
|
545
|
+
var BOOKMARK_WIDTH = 300;
|
|
546
|
+
var BOOKMARK_HEIGHT = 320;
|
|
547
|
+
var BOOKMARK_FILL = "#ffffff";
|
|
548
|
+
var BOOKMARK_STROKE = "#e8e9ea";
|
|
549
|
+
var BOOKMARK_STROKE_WIDTH = 1;
|
|
550
|
+
var BOOKMARK_RADIUS = 8;
|
|
551
|
+
var BOOKMARK_BANNER_HEIGHT = 160;
|
|
552
|
+
var BOOKMARK_BANNER_FILL = "#eceff3";
|
|
553
|
+
var BOOKMARK_PADDING = 12;
|
|
554
|
+
var BOOKMARK_GAP = 6;
|
|
555
|
+
var BOOKMARK_TITLE_HEIGHT = 38;
|
|
556
|
+
var BOOKMARK_META_HEIGHT = 16;
|
|
557
|
+
var BOOKMARK_FAVICON_SIZE = 14;
|
|
558
|
+
var BOOKMARK_TITLE_FONT_SIZE = 14;
|
|
559
|
+
var BOOKMARK_TEXT_FONT_SIZE = 12;
|
|
560
|
+
var BOOKMARK_META_FONT_SIZE = 11;
|
|
561
|
+
var BOOKMARK_TITLE_COLOR = "#1d1d1d";
|
|
562
|
+
var BOOKMARK_TEXT_COLOR = "#666666";
|
|
563
|
+
var BOOKMARK_META_COLOR = "#8f8f8f";
|
|
564
|
+
var BOOKMARK_MIN_BODY_HEIGHT = 80;
|
|
565
|
+
function getBookmarkLayout(w, h) {
|
|
566
|
+
const pad = BOOKMARK_PADDING;
|
|
567
|
+
const innerW = Math.max(0, w - pad * 2);
|
|
568
|
+
const bannerH = Math.max(0, Math.min(BOOKMARK_BANNER_HEIGHT, h - BOOKMARK_MIN_BODY_HEIGHT));
|
|
569
|
+
const metaY = Math.max(bannerH, h - pad - BOOKMARK_META_HEIGHT);
|
|
570
|
+
const titleY = bannerH + pad;
|
|
571
|
+
const titleH = Math.max(0, Math.min(BOOKMARK_TITLE_HEIGHT, metaY - BOOKMARK_GAP - titleY));
|
|
572
|
+
const descY = titleY + titleH + BOOKMARK_GAP;
|
|
573
|
+
const clamp = (r) => {
|
|
574
|
+
const x = Math.max(0, Math.min(r.x, w));
|
|
575
|
+
const y = Math.max(0, Math.min(r.y, h));
|
|
576
|
+
return { x, y, w: Math.max(0, Math.min(r.w, w - x)), h: Math.max(0, Math.min(r.h, h - y)) };
|
|
577
|
+
};
|
|
578
|
+
return {
|
|
579
|
+
banner: clamp({ x: 0, y: 0, w, h: bannerH }),
|
|
580
|
+
title: clamp({ x: pad, y: titleY, w: innerW, h: titleH }),
|
|
581
|
+
description: clamp({ x: pad, y: descY, w: innerW, h: metaY - BOOKMARK_GAP - descY }),
|
|
582
|
+
favicon: clamp({
|
|
583
|
+
x: pad,
|
|
584
|
+
y: metaY + (BOOKMARK_META_HEIGHT - BOOKMARK_FAVICON_SIZE) / 2,
|
|
585
|
+
w: BOOKMARK_FAVICON_SIZE,
|
|
586
|
+
h: BOOKMARK_FAVICON_SIZE
|
|
587
|
+
}),
|
|
588
|
+
hostname: clamp({
|
|
589
|
+
x: pad + BOOKMARK_FAVICON_SIZE + BOOKMARK_GAP,
|
|
590
|
+
y: metaY,
|
|
591
|
+
w: innerW - BOOKMARK_FAVICON_SIZE - BOOKMARK_GAP,
|
|
592
|
+
h: BOOKMARK_META_HEIGHT
|
|
593
|
+
})
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function getBookmarkHostname(url) {
|
|
597
|
+
if (!url) return "";
|
|
598
|
+
try {
|
|
599
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
600
|
+
return host.startsWith("www.") ? host.slice(4) : host;
|
|
601
|
+
} catch {
|
|
602
|
+
return "";
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function getBookmarkAsset(editor, shape) {
|
|
606
|
+
const assetId = readString(propsOf(shape), "assetId", "");
|
|
607
|
+
if (!assetId) return null;
|
|
608
|
+
const asset = editor.getAsset(assetId);
|
|
609
|
+
return asset && asset.type === "bookmark" ? asset : null;
|
|
610
|
+
}
|
|
611
|
+
function getBookmarkCard(editor, shape) {
|
|
612
|
+
const url = readString(propsOf(shape), "url", "");
|
|
613
|
+
const asset = getBookmarkAsset(editor, shape);
|
|
614
|
+
const props = asset ? asset.props : void 0;
|
|
615
|
+
return {
|
|
616
|
+
title: readString(props, "title", ""),
|
|
617
|
+
description: readString(props, "description", ""),
|
|
618
|
+
image: readString(props, "image", ""),
|
|
619
|
+
favicon: readString(props, "favicon", ""),
|
|
620
|
+
hostname: getBookmarkHostname(url) || getBookmarkHostname(readString(props, "src", "") ?? ""),
|
|
621
|
+
url,
|
|
622
|
+
hasAsset: asset !== null
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function readBookmarkBox(shape) {
|
|
626
|
+
const p = propsOf(shape);
|
|
627
|
+
return { w: readNumber(p, "w", BOOKMARK_WIDTH), h: readNumber(p, "h", BOOKMARK_HEIGHT) };
|
|
628
|
+
}
|
|
629
|
+
var ELLIPSIS = { overflow: "hidden", textOverflow: "ellipsis" };
|
|
630
|
+
function createEmptyBookmarkShape(editor, url, point) {
|
|
631
|
+
const centre = point ?? editor.getViewportPageBounds().center;
|
|
632
|
+
const id = createShapeId();
|
|
633
|
+
editor.run(() => {
|
|
634
|
+
editor.markHistoryStoppingPoint("insert bookmark");
|
|
635
|
+
editor.createShape({
|
|
636
|
+
id,
|
|
637
|
+
type: "bookmark",
|
|
638
|
+
x: centre.x - BOOKMARK_WIDTH / 2,
|
|
639
|
+
y: centre.y - BOOKMARK_HEIGHT / 2,
|
|
640
|
+
props: { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, url, assetId: null }
|
|
641
|
+
});
|
|
642
|
+
editor.setSelectedShapes([id]);
|
|
643
|
+
});
|
|
644
|
+
return editor.getShape(id);
|
|
645
|
+
}
|
|
646
|
+
var BookmarkShapeUtil = class extends BaseBoxShapeUtil {
|
|
647
|
+
static type = "bookmark";
|
|
648
|
+
static props = bookmarkShapeProps;
|
|
649
|
+
static migrations = bookmarkShapeMigrations;
|
|
650
|
+
getDefaultProps() {
|
|
651
|
+
return { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, assetId: null, url: "" };
|
|
652
|
+
}
|
|
653
|
+
getGeometry(shape) {
|
|
654
|
+
const { w, h } = readBookmarkBox(shape);
|
|
655
|
+
return new Rectangle2d({ width: Math.max(1, w), height: Math.max(1, h), isFilled: true });
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* The card is html — a banner image over wrapped, ellipsised text — which no
|
|
659
|
+
* single textured quad can stand in for, so the shape always renders through
|
|
660
|
+
* the DOM overlay.
|
|
661
|
+
*/
|
|
662
|
+
getRenderStyle(_shape) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
component(shape) {
|
|
666
|
+
const { w, h } = readBookmarkBox(shape);
|
|
667
|
+
const card = getBookmarkCard(this.editor, shape);
|
|
668
|
+
const layout = getBookmarkLayout(w, h);
|
|
669
|
+
const isEditing = this.editor.getEditingShapeId() === shape.id;
|
|
670
|
+
const frame = {
|
|
671
|
+
position: "absolute",
|
|
672
|
+
left: 0,
|
|
673
|
+
top: 0,
|
|
674
|
+
width: w,
|
|
675
|
+
height: h,
|
|
676
|
+
boxSizing: "border-box",
|
|
677
|
+
overflow: "hidden",
|
|
678
|
+
borderRadius: BOOKMARK_RADIUS,
|
|
679
|
+
background: BOOKMARK_FILL,
|
|
680
|
+
border: `${BOOKMARK_STROKE_WIDTH}px solid ${BOOKMARK_STROKE}`,
|
|
681
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
682
|
+
/*
|
|
683
|
+
* The canvas owns the pointer: a card is a shape you drag and select,
|
|
684
|
+
* not a link you click. The url is still in the DOM as an `<a>` so
|
|
685
|
+
* assistive tech can read and follow it, but it only takes the pointer
|
|
686
|
+
* while the shape is being edited.
|
|
687
|
+
*/
|
|
688
|
+
pointerEvents: isEditing ? "auto" : "none",
|
|
689
|
+
userSelect: "none"
|
|
690
|
+
};
|
|
691
|
+
return /* @__PURE__ */ jsx(
|
|
692
|
+
"a",
|
|
693
|
+
{
|
|
694
|
+
href: card.url || void 0,
|
|
695
|
+
target: "_blank",
|
|
696
|
+
rel: "noreferrer noopener",
|
|
697
|
+
draggable: false,
|
|
698
|
+
"aria-label": card.title || card.hostname || card.url || "bookmark",
|
|
699
|
+
tabIndex: isEditing ? 0 : -1,
|
|
700
|
+
style: { ...frame, display: "block", color: "inherit", textDecoration: "none" },
|
|
701
|
+
children: card.hasAsset ? this.renderCard(card, layout) : this.renderPlaceholder(card, layout)
|
|
702
|
+
}
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
/** The scraped card: banner, title, description, favicon and host. */
|
|
706
|
+
renderCard(card, layout) {
|
|
707
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
708
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", ...layout.banner, background: BOOKMARK_BANNER_FILL, overflow: "hidden" }, children: card.image ? /* @__PURE__ */ jsx(
|
|
709
|
+
"img",
|
|
710
|
+
{
|
|
711
|
+
src: card.image,
|
|
712
|
+
alt: "",
|
|
713
|
+
draggable: false,
|
|
714
|
+
style: { width: "100%", height: "100%", objectFit: "cover", pointerEvents: "none" }
|
|
715
|
+
}
|
|
716
|
+
) : null }),
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"div",
|
|
719
|
+
{
|
|
720
|
+
style: {
|
|
721
|
+
position: "absolute",
|
|
722
|
+
...layout.title,
|
|
723
|
+
...ELLIPSIS,
|
|
724
|
+
fontSize: BOOKMARK_TITLE_FONT_SIZE,
|
|
725
|
+
fontWeight: 600,
|
|
726
|
+
lineHeight: 1.3,
|
|
727
|
+
color: BOOKMARK_TITLE_COLOR
|
|
728
|
+
},
|
|
729
|
+
children: card.title
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
/* @__PURE__ */ jsx(
|
|
733
|
+
"div",
|
|
734
|
+
{
|
|
735
|
+
style: {
|
|
736
|
+
position: "absolute",
|
|
737
|
+
...layout.description,
|
|
738
|
+
...ELLIPSIS,
|
|
739
|
+
fontSize: BOOKMARK_TEXT_FONT_SIZE,
|
|
740
|
+
lineHeight: 1.4,
|
|
741
|
+
color: BOOKMARK_TEXT_COLOR
|
|
742
|
+
},
|
|
743
|
+
children: card.description
|
|
744
|
+
}
|
|
745
|
+
),
|
|
746
|
+
card.favicon ? /* @__PURE__ */ jsx(
|
|
747
|
+
"img",
|
|
748
|
+
{
|
|
749
|
+
src: card.favicon,
|
|
750
|
+
alt: "",
|
|
751
|
+
draggable: false,
|
|
752
|
+
style: { position: "absolute", ...layout.favicon, objectFit: "contain", pointerEvents: "none" }
|
|
753
|
+
}
|
|
754
|
+
) : null,
|
|
755
|
+
/* @__PURE__ */ jsx(
|
|
756
|
+
"div",
|
|
757
|
+
{
|
|
758
|
+
style: {
|
|
759
|
+
position: "absolute",
|
|
760
|
+
...layout.hostname,
|
|
761
|
+
...ELLIPSIS,
|
|
762
|
+
whiteSpace: "nowrap",
|
|
763
|
+
fontSize: BOOKMARK_META_FONT_SIZE,
|
|
764
|
+
lineHeight: `${BOOKMARK_META_HEIGHT}px`,
|
|
765
|
+
color: BOOKMARK_META_COLOR
|
|
766
|
+
},
|
|
767
|
+
children: card.hostname
|
|
768
|
+
}
|
|
769
|
+
)
|
|
770
|
+
] });
|
|
771
|
+
}
|
|
772
|
+
/** No asset: an empty banner and the host, so the shape still reads as a link to somewhere. */
|
|
773
|
+
renderPlaceholder(card, layout) {
|
|
774
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
775
|
+
/* @__PURE__ */ jsx("div", { style: { position: "absolute", ...layout.banner, background: BOOKMARK_BANNER_FILL } }),
|
|
776
|
+
/* @__PURE__ */ jsx(
|
|
777
|
+
"div",
|
|
778
|
+
{
|
|
779
|
+
style: {
|
|
780
|
+
position: "absolute",
|
|
781
|
+
...layout.title,
|
|
782
|
+
...ELLIPSIS,
|
|
783
|
+
fontSize: BOOKMARK_TITLE_FONT_SIZE,
|
|
784
|
+
lineHeight: 1.3,
|
|
785
|
+
color: BOOKMARK_META_COLOR
|
|
786
|
+
},
|
|
787
|
+
children: card.hostname || card.url
|
|
788
|
+
}
|
|
789
|
+
)
|
|
790
|
+
] });
|
|
791
|
+
}
|
|
792
|
+
getIndicatorPath(shape) {
|
|
793
|
+
const { w, h } = readBookmarkBox(shape);
|
|
794
|
+
return rectPath(w, h, BOOKMARK_RADIUS);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
var EMBED_WIDTH = 720;
|
|
798
|
+
var EMBED_HEIGHT = 500;
|
|
799
|
+
var EMBED_PLACEHOLDER_FILL = "#f5f6f8";
|
|
800
|
+
var EMBED_PLACEHOLDER_STROKE = "#9fa8b2";
|
|
801
|
+
var EMBED_PLACEHOLDER_TEXT = "#5f6670";
|
|
802
|
+
var EMBED_PLACEHOLDER_FONT_SIZE = 13;
|
|
803
|
+
var EMBED_PLACEHOLDER_PADDING = 16;
|
|
804
|
+
var EMBED_RADIUS = 6;
|
|
805
|
+
var EMBED_SANDBOX = "allow-scripts allow-same-origin allow-popups";
|
|
806
|
+
function httpsUrl(host, url, opts = {}) {
|
|
807
|
+
return `https://${host}${url.pathname}${url.search}${opts.hash ? url.hash : ""}`;
|
|
808
|
+
}
|
|
809
|
+
function youTubeId(candidate) {
|
|
810
|
+
return candidate && /^[A-Za-z0-9_-]{6,20}$/.test(candidate) ? candidate : null;
|
|
811
|
+
}
|
|
812
|
+
var youtube = {
|
|
813
|
+
type: "youtube",
|
|
814
|
+
title: "YouTube",
|
|
815
|
+
hostnames: ["youtube.com", "m.youtube.com", "youtube-nocookie.com", "youtu.be"],
|
|
816
|
+
toEmbedUrl(url) {
|
|
817
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
818
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
819
|
+
const id = host === "youtu.be" ? youTubeId(segments[0]) : youTubeId(url.searchParams.get("v")) ?? (segments[0] === "embed" || segments[0] === "shorts" || segments[0] === "live" ? youTubeId(segments[1]) : null);
|
|
820
|
+
return id ? `https://www.youtube-nocookie.com/embed/${id}` : null;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
var vimeo = {
|
|
824
|
+
type: "vimeo",
|
|
825
|
+
title: "Vimeo",
|
|
826
|
+
hostnames: ["vimeo.com", "player.vimeo.com"],
|
|
827
|
+
toEmbedUrl(url) {
|
|
828
|
+
const id = url.pathname.split("/").filter(Boolean).find((s) => /^\d{6,12}$/.test(s));
|
|
829
|
+
return id ? `https://player.vimeo.com/video/${id}` : null;
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
var codesandbox = {
|
|
833
|
+
type: "codesandbox",
|
|
834
|
+
title: "CodeSandbox",
|
|
835
|
+
hostnames: ["codesandbox.io"],
|
|
836
|
+
toEmbedUrl(url) {
|
|
837
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
838
|
+
const id = segments[0] === "p" && segments[1] === "sandbox" ? segments[2] : segments[0] === "s" || segments[0] === "embed" ? segments[1] : null;
|
|
839
|
+
return id && /^[A-Za-z0-9_-]{3,64}$/.test(id) ? `https://codesandbox.io/embed/${id}` : null;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var figma = {
|
|
843
|
+
type: "figma",
|
|
844
|
+
title: "Figma",
|
|
845
|
+
hostnames: ["figma.com"],
|
|
846
|
+
toEmbedUrl(url) {
|
|
847
|
+
if (url.pathname === "/embed") return null;
|
|
848
|
+
const target = `https://www.figma.com${url.pathname}${url.search}`;
|
|
849
|
+
return `https://www.figma.com/embed?embed_host=mocanvas&url=${encodeURIComponent(target)}`;
|
|
850
|
+
},
|
|
851
|
+
// The one built-in whose embed form is not also a valid input to its own
|
|
852
|
+
// `toEmbedUrl`: wrapping an already-wrapped url would nest it twice.
|
|
853
|
+
fromEmbedUrl(url) {
|
|
854
|
+
if (url.pathname !== "/embed") return null;
|
|
855
|
+
const target = url.searchParams.get("url");
|
|
856
|
+
if (!target) return null;
|
|
857
|
+
try {
|
|
858
|
+
const parsed = new URL(target);
|
|
859
|
+
return parsed.protocol === "https:" && normalizeHost(parsed.hostname) === "figma.com" ? parsed.href : null;
|
|
860
|
+
} catch {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
var googleMaps = {
|
|
866
|
+
type: "google-maps",
|
|
867
|
+
title: "Google Maps",
|
|
868
|
+
hostnames: ["google.com", "maps.google.com"],
|
|
869
|
+
toEmbedUrl(url) {
|
|
870
|
+
if (!url.pathname.startsWith("/maps")) return null;
|
|
871
|
+
if (url.pathname.startsWith("/maps/embed")) return httpsUrl("www.google.com", url);
|
|
872
|
+
const params = new URLSearchParams(url.search);
|
|
873
|
+
params.set("output", "embed");
|
|
874
|
+
return `https://www.google.com/maps?${params.toString()}`;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
var excalidraw = {
|
|
878
|
+
type: "excalidraw",
|
|
879
|
+
title: "Excalidraw",
|
|
880
|
+
hostnames: ["excalidraw.com"],
|
|
881
|
+
// The scene lives in the fragment (`#json=`, `#room=`), so it has to survive.
|
|
882
|
+
toEmbedUrl: (url) => httpsUrl("excalidraw.com", url, { hash: true })
|
|
883
|
+
};
|
|
884
|
+
var DEFAULT_EMBED_DEFINITIONS = [youtube, vimeo, codesandbox, figma, googleMaps, excalidraw];
|
|
885
|
+
var embedDefinitions = [...DEFAULT_EMBED_DEFINITIONS];
|
|
886
|
+
var EMPTY_EMBED_CONFIG = Object.freeze({});
|
|
887
|
+
function normalizeHost(hostname) {
|
|
888
|
+
const host = hostname.toLowerCase();
|
|
889
|
+
return host.startsWith("www.") ? host.slice(4) : host;
|
|
890
|
+
}
|
|
891
|
+
function getEmbedDefinition(url, definitions = embedDefinitions, config = EMPTY_EMBED_CONFIG) {
|
|
892
|
+
if (typeof url !== "string" || url.length === 0) return null;
|
|
893
|
+
let parsed;
|
|
894
|
+
try {
|
|
895
|
+
parsed = new URL(url);
|
|
896
|
+
} catch {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
900
|
+
const host = normalizeHost(parsed.hostname);
|
|
901
|
+
for (const definition of definitions) {
|
|
902
|
+
if (!definition.hostnames.some((h) => normalizeHost(h) === host)) continue;
|
|
903
|
+
const embedUrl = definition.toEmbedUrl(parsed, config[definition.type]);
|
|
904
|
+
if (embedUrl && embedUrl.startsWith("https://")) return { definition, embedUrl };
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
function getEmbedInfo(definitions, url, config = EMPTY_EMBED_CONFIG) {
|
|
910
|
+
const match = getEmbedDefinition(url, definitions, config);
|
|
911
|
+
if (match !== null) return { definition: match.definition, url, embedUrl: match.embedUrl };
|
|
912
|
+
if (typeof url !== "string" || url.length === 0) return void 0;
|
|
913
|
+
let parsed;
|
|
914
|
+
try {
|
|
915
|
+
parsed = new URL(url);
|
|
916
|
+
} catch {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
|
|
920
|
+
const host = normalizeHost(parsed.hostname);
|
|
921
|
+
for (const definition of definitions) {
|
|
922
|
+
if (definition.fromEmbedUrl === void 0) continue;
|
|
923
|
+
if (!definition.hostnames.some((h) => normalizeHost(h) === host)) continue;
|
|
924
|
+
let pageUrl;
|
|
925
|
+
try {
|
|
926
|
+
pageUrl = definition.fromEmbedUrl(parsed);
|
|
927
|
+
} catch {
|
|
928
|
+
pageUrl = null;
|
|
929
|
+
}
|
|
930
|
+
if (!pageUrl) continue;
|
|
931
|
+
return { definition, url: pageUrl, embedUrl: parsed.href };
|
|
932
|
+
}
|
|
933
|
+
return void 0;
|
|
934
|
+
}
|
|
935
|
+
function readEmbedBox(shape) {
|
|
936
|
+
const p = propsOf(shape);
|
|
937
|
+
return { w: readNumber(p, "w", EMBED_WIDTH), h: readNumber(p, "h", EMBED_HEIGHT) };
|
|
938
|
+
}
|
|
939
|
+
function getEmbedDisplayValues(editor, shape, theme, colorMode) {
|
|
940
|
+
return {
|
|
941
|
+
...getDefaultDisplayValues(editor, shape, theme, colorMode),
|
|
942
|
+
placeholderFill: EMBED_PLACEHOLDER_FILL,
|
|
943
|
+
placeholderStroke: EMBED_PLACEHOLDER_STROKE,
|
|
944
|
+
placeholderTextColor: EMBED_PLACEHOLDER_TEXT,
|
|
945
|
+
cornerRadius: EMBED_RADIUS
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
var EMBED_SHAPE_PERMISSION_NAMES = [
|
|
949
|
+
"accelerometer",
|
|
950
|
+
"autoplay",
|
|
951
|
+
"camera",
|
|
952
|
+
"clipboard-write",
|
|
953
|
+
"encrypted-media",
|
|
954
|
+
"fullscreen",
|
|
955
|
+
"geolocation",
|
|
956
|
+
"gyroscope",
|
|
957
|
+
"microphone",
|
|
958
|
+
"picture-in-picture"
|
|
959
|
+
];
|
|
960
|
+
var unknownEmbedShapePermissionOverrides = {};
|
|
961
|
+
function embedShapePermissionsToAllow(permissions) {
|
|
962
|
+
const granted = EMBED_SHAPE_PERMISSION_NAMES.filter((name) => permissions[name] === true);
|
|
963
|
+
return granted.length === 0 ? void 0 : granted.join("; ");
|
|
964
|
+
}
|
|
965
|
+
var EmbedShapeUtil = class extends BaseBoxShapeUtil {
|
|
966
|
+
static type = "embed";
|
|
967
|
+
static props = embedShapeProps;
|
|
968
|
+
static migrations = embedShapeMigrations;
|
|
969
|
+
static options = { getDefaultDisplayValues: getEmbedDisplayValues };
|
|
970
|
+
getDefaultProps() {
|
|
971
|
+
return { w: EMBED_WIDTH, h: EMBED_HEIGHT, url: "" };
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* The per-service settings this util was configured with, keyed by
|
|
975
|
+
* {@link EmbedDefinition.type}.
|
|
976
|
+
*
|
|
977
|
+
* Always an object, so a definition can index it without a guard. This is
|
|
978
|
+
* where an api key belongs: a definition is handed its own entry and never
|
|
979
|
+
* reaches for `process.env` itself, which would put the secret in every
|
|
980
|
+
* bundle that imported the definition and make it the same for every editor
|
|
981
|
+
* on the page.
|
|
982
|
+
*/
|
|
983
|
+
get embedConfig() {
|
|
984
|
+
return this.options.embedConfig ?? EMPTY_EMBED_CONFIG;
|
|
985
|
+
}
|
|
986
|
+
/** The permit list in force for this util; see {@link EmbedShapeOptions.embedDefinitions}. */
|
|
987
|
+
getEmbedDefinitions() {
|
|
988
|
+
return this.options.embedDefinitions ?? embedDefinitions;
|
|
989
|
+
}
|
|
990
|
+
/** This shape's permit-list entry and iframe url, or `null` when it has none. */
|
|
991
|
+
getEmbedMatch(shape) {
|
|
992
|
+
return getEmbedDefinition(readString(propsOf(shape), "url", ""), this.getEmbedDefinitions(), this.embedConfig);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* The width ÷ height this embed's content wants, or `undefined` when nothing
|
|
996
|
+
* has an opinion about it.
|
|
997
|
+
*
|
|
998
|
+
* Public because the app needs the same answer the util uses: a "fit to
|
|
999
|
+
* content" command, a paste handler placing a new embed, a layout engine
|
|
1000
|
+
* reserving a slot. It comes from the matching definition's
|
|
1001
|
+
* {@link EmbedDefinition.sizeToContentAspectRatio}; a url off the permit
|
|
1002
|
+
* list, or a service with no fixed shape, answers `undefined`.
|
|
1003
|
+
*/
|
|
1004
|
+
resolveAspectRatio(shape) {
|
|
1005
|
+
const ratio = this.getEmbedMatch(shape)?.definition.sizeToContentAspectRatio;
|
|
1006
|
+
return typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0 ? ratio : void 0;
|
|
1007
|
+
}
|
|
1008
|
+
getGeometry(shape) {
|
|
1009
|
+
const { w, h } = readEmbedBox(shape);
|
|
1010
|
+
return new Rectangle2d({ width: Math.max(1, w), height: Math.max(1, h), isFilled: true });
|
|
1011
|
+
}
|
|
1012
|
+
/** An iframe is not a quad: embeds always render through the DOM overlay. */
|
|
1013
|
+
getRenderStyle(_shape) {
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
component(shape) {
|
|
1017
|
+
const { w, h } = readEmbedBox(shape);
|
|
1018
|
+
const url = readString(propsOf(shape), "url", "");
|
|
1019
|
+
const match = this.getEmbedMatch(shape);
|
|
1020
|
+
const box = {
|
|
1021
|
+
position: "absolute",
|
|
1022
|
+
left: 0,
|
|
1023
|
+
top: 0,
|
|
1024
|
+
width: w,
|
|
1025
|
+
height: h,
|
|
1026
|
+
boxSizing: "border-box",
|
|
1027
|
+
overflow: "hidden",
|
|
1028
|
+
borderRadius: EMBED_RADIUS
|
|
1029
|
+
};
|
|
1030
|
+
if (!match) {
|
|
1031
|
+
return /* @__PURE__ */ jsx(
|
|
1032
|
+
"div",
|
|
1033
|
+
{
|
|
1034
|
+
style: {
|
|
1035
|
+
...box,
|
|
1036
|
+
background: EMBED_PLACEHOLDER_FILL,
|
|
1037
|
+
border: `1px dashed ${EMBED_PLACEHOLDER_STROKE}`,
|
|
1038
|
+
padding: EMBED_PLACEHOLDER_PADDING,
|
|
1039
|
+
display: "flex",
|
|
1040
|
+
alignItems: "center",
|
|
1041
|
+
justifyContent: "center",
|
|
1042
|
+
textAlign: "center",
|
|
1043
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
1044
|
+
fontSize: EMBED_PLACEHOLDER_FONT_SIZE,
|
|
1045
|
+
color: EMBED_PLACEHOLDER_TEXT,
|
|
1046
|
+
wordBreak: "break-all",
|
|
1047
|
+
pointerEvents: "none",
|
|
1048
|
+
userSelect: "none"
|
|
1049
|
+
},
|
|
1050
|
+
"aria-label": url ? `embed: ${url}` : "embed",
|
|
1051
|
+
children: url || "No embed url"
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
const isEditing = this.editor.getEditingShapeId() === shape.id;
|
|
1056
|
+
return /* @__PURE__ */ jsx("div", { style: box, children: /* @__PURE__ */ jsx(
|
|
1057
|
+
"iframe",
|
|
1058
|
+
{
|
|
1059
|
+
src: match.embedUrl,
|
|
1060
|
+
title: `${match.definition.title} embed`,
|
|
1061
|
+
width: w,
|
|
1062
|
+
height: h,
|
|
1063
|
+
sandbox: EMBED_SANDBOX,
|
|
1064
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
1065
|
+
loading: "lazy",
|
|
1066
|
+
allowFullScreen: true,
|
|
1067
|
+
style: { display: "block", width: "100%", height: "100%", border: 0, pointerEvents: isEditing ? "auto" : "none" }
|
|
1068
|
+
}
|
|
1069
|
+
) });
|
|
1070
|
+
}
|
|
1071
|
+
getIndicatorPath(shape) {
|
|
1072
|
+
const { w, h } = readEmbedBox(shape);
|
|
1073
|
+
return rectPath(w, h, EMBED_RADIUS);
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* "Editing" an embed is not typing into it — the shape has no text of its own.
|
|
1077
|
+
* It is the state in which the embedded page takes the pointer, and it is the
|
|
1078
|
+
* state the iframe above waits for.
|
|
1079
|
+
*
|
|
1080
|
+
* Saying no here left that state unreachable, so the frame stayed inert for
|
|
1081
|
+
* good and the embed was a picture of a page rather than a page: a video that
|
|
1082
|
+
* would not play, a map that would not pan. Saying yes gives an embed the same
|
|
1083
|
+
* two-step every canvas uses for something that wants the pointer for itself —
|
|
1084
|
+
* one click selects the shape and its handles, a double click hands the
|
|
1085
|
+
* pointer over, and clicking away takes it back.
|
|
1086
|
+
*/
|
|
1087
|
+
canEdit(_shape) {
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* An embed whose service declared a fixed content shape resizes
|
|
1092
|
+
* proportionally; anything else is a free box.
|
|
1093
|
+
*/
|
|
1094
|
+
// SEMANTICS-ASSUMED: tying the lock to `sizeToContentAspectRatio` is the
|
|
1095
|
+
// reading that makes the two features one feature — the ratio is declared
|
|
1096
|
+
// once and both sizing paths honour it — and it is a no-op for every
|
|
1097
|
+
// built-in, none of which declares one.
|
|
1098
|
+
isAspectRatioLocked(shape) {
|
|
1099
|
+
return this.resolveAspectRatio(shape) !== void 0;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* A new embed is created at its service's content ratio, keeping the width
|
|
1103
|
+
* it was asked for. Nothing happens for a service with no declared ratio.
|
|
1104
|
+
*/
|
|
1105
|
+
onBeforeCreate(next) {
|
|
1106
|
+
const ratio = this.resolveAspectRatio(next);
|
|
1107
|
+
if (ratio === void 0) return;
|
|
1108
|
+
const { w } = readEmbedBox(next);
|
|
1109
|
+
const h = Math.max(1, w / ratio);
|
|
1110
|
+
if (h === readEmbedBox(next).h) return;
|
|
1111
|
+
return { ...next, props: { ...next.props, h } };
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
export { BOOKMARK_BANNER_FILL, BOOKMARK_BANNER_HEIGHT, BOOKMARK_FAVICON_SIZE, BOOKMARK_FILL, BOOKMARK_GAP, BOOKMARK_HEIGHT, BOOKMARK_META_COLOR, BOOKMARK_META_FONT_SIZE, BOOKMARK_META_HEIGHT, BOOKMARK_MIN_BODY_HEIGHT, BOOKMARK_PADDING, BOOKMARK_RADIUS, BOOKMARK_STROKE, BOOKMARK_STROKE_WIDTH, BOOKMARK_TEXT_COLOR, BOOKMARK_TEXT_FONT_SIZE, BOOKMARK_TITLE_COLOR, BOOKMARK_TITLE_FONT_SIZE, BOOKMARK_TITLE_HEIGHT, BOOKMARK_WIDTH, BookmarkShapeUtil, DEFAULT_EMBED_DEFINITIONS, EMBED_HEIGHT, EMBED_PLACEHOLDER_FILL, EMBED_PLACEHOLDER_FONT_SIZE, EMBED_PLACEHOLDER_PADDING, EMBED_PLACEHOLDER_STROKE, EMBED_PLACEHOLDER_TEXT, EMBED_RADIUS, EMBED_SANDBOX, EMBED_SHAPE_PERMISSION_NAMES, EMBED_WIDTH, EmbedShapeUtil, RICH_TEXT_MARKS, RICH_TEXT_NODES, applyPlainTextToRichText, arrowBindingMigrations, arrowBindingProps, arrowShapeMigrations, arrowShapeProps, arrowShapeVersions, asRichText, bookmarkShapeMigrations, bookmarkShapeProps, boxPath, canBuildPath, createEmptyBookmarkShape, drawShapeMigrations, drawShapeProps, embedDefinitions, embedShapeMigrations, embedShapePermissionsToAllow, embedShapeProps, escapeHtml, frameShapeMigrations, frameShapeProps, geoShapeProps, getBookmarkAsset, getBookmarkCard, getBookmarkHostname, getBookmarkLayout, getEmbedDefinition, getEmbedDisplayValues, getEmbedInfo, getRichTextEditorFactory, groupShapeMigrations, groupShapeProps, highlightShapeMigrations, highlightShapeProps, imageShapeMigrations, imageShapeProps, isRichText, lineShapeMigrations, lineShapeProps, loadTipTapStarterExtensions, noteShapeMigrations, noteShapeProps, polylinePath, propsOf, readArray, readBoolean, readEnum, readNumber, readPoint, readRecord, readRichText, readString, readStyle, readText, rectPath, registerRichTextEditorFactory, richTextEquals, richTextToBlocks, richTextToHtml, richTextToText, richTextValidator, safeHref, svgPath, textShapeMigrations, textShapeProps, tipTapDefaultExtensions, toRichText, unknownEmbedShapePermissionOverrides, videoShapeMigrations, videoShapeProps };
|
|
1116
|
+
//# sourceMappingURL=chunk-OCYJAMXT.js.map
|
|
1117
|
+
//# sourceMappingURL=chunk-OCYJAMXT.js.map
|