@mocanvas/mocanvas 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +8 -2
- package/BENCHMARK.md +244 -84
- package/MIGRATION.md +46 -22
- package/README.md +57 -24
- package/UI.md +24 -2
- package/dist/{chunk-TWCVRVK2.js → chunk-3F2HKY37.js} +3 -3
- package/dist/{chunk-TWCVRVK2.js.map → chunk-3F2HKY37.js.map} +1 -1
- package/dist/{chunk-DMCI6V5Y.js → chunk-HRUFJLK5.js} +3 -3
- package/dist/{chunk-DMCI6V5Y.js.map → chunk-HRUFJLK5.js.map} +1 -1
- package/dist/{chunk-OCYJAMXT.js → chunk-S7FPGQAA.js} +22 -3
- package/dist/chunk-S7FPGQAA.js.map +1 -0
- package/dist/{chunk-QDUQZXE4.js → chunk-YMUR46N6.js} +456 -19
- package/dist/chunk-YMUR46N6.js.map +1 -0
- package/dist/{export-VQGOVONC.js → export-UA6X35ZW.js} +4 -4
- package/dist/{export-VQGOVONC.js.map → export-UA6X35ZW.js.map} +1 -1
- package/dist/index.d.ts +279 -35
- package/dist/index.js +404 -410
- package/dist/index.js.map +1 -1
- package/dist/{ui-4C5V5GYT.css → mocanvas.css} +73 -241
- package/dist/mocanvas.css.map +1 -0
- package/dist/panel-shortcuts-4GIFCQQR.js +4 -0
- package/dist/{panel-shortcuts-R2PDTDUN.js.map → panel-shortcuts-4GIFCQQR.js.map} +1 -1
- package/dist/urlContent-WYYVQYYN.js +4 -0
- package/dist/{urlContent-GC44EU52.js.map → urlContent-WYYVQYYN.js.map} +1 -1
- package/package.json +8 -4
- package/dist/chunk-OCYJAMXT.js.map +0 -1
- package/dist/chunk-QDUQZXE4.js.map +0 -1
- package/dist/panel-shortcuts-R2PDTDUN.js +0 -4
- package/dist/urlContent-GC44EU52.js +0 -4
package/README.md
CHANGED
|
@@ -36,33 +36,41 @@ Creating shapes, median milliseconds — lower is better:
|
|
|
36
36
|
|
|
37
37
|
| Scene | mocanvas | tldraw 5.4 | |
|
|
38
38
|
| :--- | ---: | ---: | ---: |
|
|
39
|
-
| 1,000 geo shapes |
|
|
40
|
-
| 5,000 geo shapes |
|
|
41
|
-
| 20,000 geo shapes |
|
|
42
|
-
| 1,000 mixed |
|
|
43
|
-
| 5,000 mixed |
|
|
44
|
-
| 20,000 mixed |
|
|
39
|
+
| 1,000 geo shapes | 17 ms | 44 ms | **2.6× faster** |
|
|
40
|
+
| 5,000 geo shapes | 43 ms | 131 ms | **3.1× faster** |
|
|
41
|
+
| 20,000 geo shapes | 145 ms | 387 ms | **2.7× faster** |
|
|
42
|
+
| 1,000 mixed | 29 ms | 70 ms | **2.4× faster** |
|
|
43
|
+
| 5,000 mixed | 80 ms | 191 ms | **2.4× faster** |
|
|
44
|
+
| 20,000 mixed | 234 ms | 609 ms | **2.6× faster** |
|
|
45
45
|
|
|
46
46
|
Interaction at scale, dragging a selection, median milliseconds per frame:
|
|
47
47
|
|
|
48
48
|
| Scene | mocanvas | tldraw 5.4 | |
|
|
49
49
|
| :--- | ---: | ---: | ---: |
|
|
50
|
-
| 1,000 geo shapes |
|
|
51
|
-
| 5,000 geo shapes |
|
|
52
|
-
| 20,000 geo shapes |
|
|
53
|
-
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
| 1,000 geo shapes | 9.2 ms | 16.7 ms | **1.8× faster** |
|
|
51
|
+
| 5,000 geo shapes | 58.3 ms | 108.3 ms | **1.9× faster** |
|
|
52
|
+
| 20,000 geo shapes | 233.3 ms | 541.6 ms | **2.3× faster** |
|
|
53
|
+
| 1,000 mixed | 16.6 ms | 16.8 ms | a tie, at the frame floor |
|
|
54
|
+
| 5,000 mixed | 66.6 ms | 116.4 ms | **1.8× faster** |
|
|
55
|
+
| 20,000 mixed | 280.9 ms | 608.4 ms | **2.2× faster** |
|
|
56
|
+
|
|
57
|
+
mocanvas scales: about 2.6× on building a scene at every size, and the
|
|
58
|
+
interaction gap widens as the scene grows — 1.8× at a thousand shapes, 2.3× at
|
|
59
|
+
twenty thousand.
|
|
60
|
+
|
|
61
|
+
**With their caveats.** Measured on an Apple M3 Pro, headless Chromium on the
|
|
62
|
+
Metal backend, default settings on both sides. Two things count against
|
|
63
|
+
mocanvas and are worth knowing: every shape here uses the hand-drawn stroke,
|
|
64
|
+
which is its most expensive path, and pan and zoom is left out entirely because
|
|
65
|
+
mocanvas sits at the harness's own frame floor in every case — it cannot be
|
|
66
|
+
measured, so it is not claimed. It is our own benchmark of our own library.
|
|
67
|
+
|
|
68
|
+
An earlier version of this table was measured under software rasterisation and
|
|
69
|
+
was wrong in both directions: it flattered mocanvas on creation (~3× where the
|
|
70
|
+
truth is ~2.6×) and reported a loss at a thousand shapes that turned out to be an
|
|
71
|
+
artefact of the CPU rasteriser rather than anything either library did.
|
|
72
|
+
|
|
73
|
+
`BENCHMARK.md`, shipped in this package, has the method, the full tables, the rest of the caveats, and a
|
|
66
74
|
rendering-fidelity comparison against tldraw at 99.2% interior IoU.
|
|
67
75
|
|
|
68
76
|
## Compatibility with tldraw
|
|
@@ -104,11 +112,32 @@ npm install @mocanvas/mocanvas react react-dom
|
|
|
104
112
|
|
|
105
113
|
`react` and `react-dom` (>= 18) are peer dependencies.
|
|
106
114
|
|
|
115
|
+
Then import the stylesheet once, anywhere in your app:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import "@mocanvas/mocanvas/mocanvas.css"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**This line is required.** Without it the canvas still renders, but the
|
|
122
|
+
toolbar, panels, menus and dialogs come up unstyled. It is the same arrangement
|
|
123
|
+
as `tldraw/tldraw.css`.
|
|
124
|
+
|
|
125
|
+
> **Upgrading from 4.0.2 or earlier?** Add that line — it is the only change.
|
|
126
|
+
> Until 4.0.2 `@mocanvas/mocanvas` imported the stylesheet from its own
|
|
127
|
+
> JavaScript entry, which worked in a bundler and broke everywhere else:
|
|
128
|
+
> vitest, SSR, or any plain Node `import` threw
|
|
129
|
+
> `ERR_UNKNOWN_FILE_EXTENSION: Unknown file extension ".css"`, because Node has
|
|
130
|
+
> no loader for CSS. The file was also content-hashed
|
|
131
|
+
> (`dist/ui-4C5V5GYT.css`), so nothing could import it by name. The JS entry no
|
|
132
|
+
> longer imports CSS and the stylesheet now has a fixed path, so your app
|
|
133
|
+
> imports it.
|
|
134
|
+
|
|
107
135
|
## Use
|
|
108
136
|
|
|
109
137
|
```tsx
|
|
110
138
|
import { createRoot } from "react-dom/client"
|
|
111
139
|
import { Mocanvas, createShapeId, type Editor } from "@mocanvas/mocanvas"
|
|
140
|
+
import "@mocanvas/mocanvas/mocanvas.css"
|
|
112
141
|
|
|
113
142
|
function App() {
|
|
114
143
|
function onMount(editor: Editor) {
|
|
@@ -134,8 +163,12 @@ function App() {
|
|
|
134
163
|
createRoot(document.getElementById("root")!).render(<App />)
|
|
135
164
|
```
|
|
136
165
|
|
|
137
|
-
`<Mocanvas />` fills its container, so give the container a size.
|
|
138
|
-
|
|
166
|
+
`<Mocanvas />` fills its container, so give the container a size.
|
|
167
|
+
|
|
168
|
+
The default UI stylesheet is `@mocanvas/mocanvas/mocanvas.css` and your app
|
|
169
|
+
imports it — the package's JavaScript entry deliberately does not, so that
|
|
170
|
+
importing `@mocanvas/mocanvas` works under Node as well as under a bundler.
|
|
171
|
+
`UI.md`, shipped in this package, covers restyling it.
|
|
139
172
|
|
|
140
173
|
## ESM only
|
|
141
174
|
|
package/UI.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
style panel, an optional statistics chip, and the chrome the editor draws on the
|
|
5
5
|
canvas itself. All of it is optional and all of it is themeable from CSS.
|
|
6
6
|
|
|
7
|
+
## Loading the stylesheet
|
|
8
|
+
|
|
9
|
+
The theme is published as `@mocanvas/mocanvas/mocanvas.css` and your app
|
|
10
|
+
imports it:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import "@mocanvas/mocanvas/mocanvas.css"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The package's JavaScript entry does not import it. That is deliberate: Node has
|
|
17
|
+
no loader for `.css`, so a CSS import inside `dist/index.js` makes
|
|
18
|
+
`import "@mocanvas/mocanvas"` throw `ERR_UNKNOWN_FILE_EXTENSION` anywhere there
|
|
19
|
+
is no bundler — vitest, SSR, a plain script. Up to and including 4.0.2 it did,
|
|
20
|
+
and the emitted file was content-hashed (`dist/ui-4C5V5GYT.css`) so there was no
|
|
21
|
+
stable name to import instead. Both are fixed; the cost is this one line in your
|
|
22
|
+
app.
|
|
23
|
+
|
|
24
|
+
`<Canvas />` from `@mocanvas/editor` carries fallback values for the canvas
|
|
25
|
+
chrome tokens, so an editor mounted without the stylesheet still renders
|
|
26
|
+
legibly — but the toolbar, panels, menus and dialogs need it.
|
|
27
|
+
|
|
7
28
|
Source lives in `packages/mocanvas/src/ui/`:
|
|
8
29
|
|
|
9
30
|
| File | What it holds |
|
|
@@ -122,8 +143,9 @@ so inline marks like the "mixed" badge keep the size they ask for.
|
|
|
122
143
|
<Mocanvas hideUi />
|
|
123
144
|
```
|
|
124
145
|
|
|
125
|
-
This drops the toolbar, zoom bar, style panel and stats chip.
|
|
126
|
-
|
|
146
|
+
This drops the toolbar, zoom bar, style panel and stats chip. The stylesheet is
|
|
147
|
+
imported by your app, not by the component, so the canvas chrome tokens above
|
|
148
|
+
keep working. Default keyboard
|
|
127
149
|
shortcuts are wired by `<Mocanvas />` itself and are unaffected; call
|
|
128
150
|
`useKeyboardShortcuts(editor)` yourself if you build on `<Canvas />` directly.
|
|
129
151
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getEmbedDefinition, EMBED_HEIGHT, EMBED_WIDTH, BOOKMARK_HEIGHT, BOOKMARK_WIDTH } from './chunk-
|
|
1
|
+
import { getEmbedDefinition, EMBED_HEIGHT, EMBED_WIDTH, BOOKMARK_HEIGHT, BOOKMARK_WIDTH } from './chunk-S7FPGQAA.js';
|
|
2
2
|
import { AssetRecordType, createShapeId } from '@mocanvas/editor';
|
|
3
3
|
|
|
4
4
|
var DEFAULT_MAX_IMAGE_DIMENSION = 1e3;
|
|
@@ -292,5 +292,5 @@ function createBookmarkShape(editor, url, asset, point) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
export { BOOKMARK_UNFURL_FAILED, DEFAULT_MAX_IMAGE_DIMENSION, classifyExternalText, createBookmarkShape, createEmbedShape, createImageAssetFromFile, createImageAssetFromSvgText, createImageShapesForAssets, createTextShapeAt, defaultHandleExternalUrlContent, fitImageSize, getSvgTextSize, isAnimatedImageType, isImageFile, isSvgFile, loadImageSizeInBrowser, looksLikeSvg, looksLikeUrl, readFileAsDataUrl, registerDefaultExternalContentHandlers, svgTextToDataUrl };
|
|
295
|
-
//# sourceMappingURL=chunk-
|
|
296
|
-
//# sourceMappingURL=chunk-
|
|
295
|
+
//# sourceMappingURL=chunk-3F2HKY37.js.map
|
|
296
|
+
//# sourceMappingURL=chunk-3F2HKY37.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/external/defaultExternalContentHandlers.ts","../src/external/urlContent.ts"],"names":["pointOrCenter","createShapeId"],"mappings":"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","file":"chunk-TWCVRVK2.js","sourcesContent":["import { AssetRecordType, createShapeId, type Editor, type ExternalContent, type ImageAsset, type ShapeId, type VecLike } from \"@mocanvas/editor\"\nimport type { ImageShape } from \"../shapes/ImageShapeUtil\"\nimport type { TextShape } from \"../shapes/TextShapeUtil\"\nimport { defaultHandleExternalUrlContent, type ExternalUrlContentOptions } from \"./urlContent\"\n\nexport interface ImageSize {\n w: number\n h: number\n}\n\n/** Resolves an image's pixel size from its source; injectable so tests can run without a DOM. */\nexport type ImageSizeLoader = (src: string, file?: File) => Promise<ImageSize>\n\nexport interface ExternalContentOptions {\n /** Longest side an inserted image may have, in page units. */\n maxImageDimension?: number\n /** Fallback size when an image's dimensions cannot be read. */\n fallbackImageSize?: ImageSize\n loadImageSize?: ImageSizeLoader\n readFileAsDataUrl?: (file: File) => Promise<string>\n /** Offset between shapes when several are inserted at the same point. */\n stackOffset?: number\n}\n\n/** Everything {@link registerDefaultExternalContentHandlers} accepts. */\nexport type DefaultExternalContentOptions = ExternalContentOptions & ExternalUrlContentOptions\n\nexport const DEFAULT_MAX_IMAGE_DIMENSION = 1000\nconst DEFAULT_FALLBACK_IMAGE_SIZE: ImageSize = { w: 100, h: 100 }\nconst DEFAULT_STACK_OFFSET = 20\n\n/** Scale `size` down (never up) so both sides fit within `max`, keeping the aspect ratio. */\nexport function fitImageSize(size: ImageSize, max = DEFAULT_MAX_IMAGE_DIMENSION): ImageSize {\n const w = Math.max(1, size.w)\n const h = Math.max(1, size.h)\n const scale = Math.min(1, max / Math.max(w, h))\n return { w: Math.round(w * scale), h: Math.round(h * scale) }\n}\n\nexport function isImageFile(file: File): boolean {\n return file.type.startsWith(\"image/\")\n}\n\nexport function isSvgFile(file: File): boolean {\n return file.type === \"image/svg+xml\" || /\\.svg$/i.test(file.name)\n}\n\nexport function isAnimatedImageType(mimeType: string): boolean {\n return mimeType === \"image/gif\" || mimeType === \"image/apng\" || mimeType === \"image/webp\"\n}\n\nconst URL_RE = /^(https?:\\/\\/|mailto:)\\S+$/i\n\nexport function looksLikeUrl(text: string): boolean {\n return URL_RE.test(text.trim())\n}\n\nexport function looksLikeSvg(text: string): boolean {\n return /^\\s*(<\\?xml[^>]*>\\s*)?(<!DOCTYPE[^>]*>\\s*)?<svg[\\s>]/i.test(text)\n}\n\n/** Read a file into a `data:` URL, via `FileReader` where present and `arrayBuffer()` elsewhere. */\nexport async function readFileAsDataUrl(file: File): Promise<string> {\n if (typeof FileReader !== \"undefined\") {\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(reader.error ?? new Error(\"Could not read file\"))\n reader.readAsDataURL(file)\n })\n }\n const bytes = new Uint8Array(await file.arrayBuffer())\n let binary = \"\"\n const chunk = 0x8000\n for (let i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode(...bytes.subarray(i, i + chunk))\n return `data:${file.type || \"application/octet-stream\"};base64,${btoa(binary)}`\n}\n\n/** A `data:` URL for an SVG document string. */\nexport function svgTextToDataUrl(text: string): string {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(text)}`\n}\n\n/** Pixel size declared by an SVG's root `width`/`height` or `viewBox`, or `null` when neither is usable. */\nexport function getSvgTextSize(text: string): ImageSize | null {\n const root = /<svg\\b[^>]*>/i.exec(text)?.[0]\n if (!root) return null\n const attr = (name: string) => new RegExp(`\\\\s${name}\\\\s*=\\\\s*[\"']([^\"']+)[\"']`, \"i\").exec(root)?.[1]\n const px = (v: string | undefined) => {\n if (!v || /%$/.test(v)) return undefined\n const n = parseFloat(v)\n return Number.isFinite(n) && n > 0 ? n : undefined\n }\n const w = px(attr(\"width\"))\n const h = px(attr(\"height\"))\n if (w && h) return { w, h }\n const viewBox = attr(\"viewBox\")\n ?.split(/[\\s,]+/)\n .map(Number)\n if (viewBox && viewBox.length === 4 && viewBox[2]! > 0 && viewBox[3]! > 0) return { w: viewBox[2]!, h: viewBox[3]! }\n return null\n}\n\n/** Default size loader: `createImageBitmap` when available, else an `Image` element. */\nexport const loadImageSizeInBrowser: ImageSizeLoader = async (src, file) => {\n if (file && typeof createImageBitmap === \"function\" && !isSvgFile(file)) {\n try {\n const bitmap = await createImageBitmap(file)\n const size = { w: bitmap.width, h: bitmap.height }\n bitmap.close()\n return size\n } catch {\n // fall through to the Image element (e.g. unsupported type)\n }\n }\n if (typeof Image === \"undefined\") throw new Error(\"No image decoder available\")\n return new Promise<ImageSize>((resolve, reject) => {\n const img = new Image()\n img.onload = () => resolve({ w: img.naturalWidth, h: img.naturalHeight })\n img.onerror = () => reject(new Error(\"Could not decode image\"))\n img.src = src\n })\n}\n\ntype ResolvedOptions = Required<ExternalContentOptions>\n\nfunction resolveOptions(opts: ExternalContentOptions): ResolvedOptions {\n return {\n maxImageDimension: opts.maxImageDimension ?? DEFAULT_MAX_IMAGE_DIMENSION,\n fallbackImageSize: opts.fallbackImageSize ?? DEFAULT_FALLBACK_IMAGE_SIZE,\n loadImageSize: opts.loadImageSize ?? loadImageSizeInBrowser,\n readFileAsDataUrl: opts.readFileAsDataUrl ?? readFileAsDataUrl,\n stackOffset: opts.stackOffset ?? DEFAULT_STACK_OFFSET,\n }\n}\n\n/** Build (without storing) an image asset for a file; `undefined` for non-image files. */\nexport async function createImageAssetFromFile(file: File, opts: ExternalContentOptions = {}): Promise<ImageAsset | undefined> {\n if (!isImageFile(file) && !isSvgFile(file)) return undefined\n const o = resolveOptions(opts)\n const src = await o.readFileAsDataUrl(file)\n let size: ImageSize\n try {\n size = await o.loadImageSize(src, file)\n } catch {\n size = o.fallbackImageSize\n }\n const mimeType = file.type || (isSvgFile(file) ? \"image/svg+xml\" : null)\n return {\n id: AssetRecordType.createId(),\n typeName: \"asset\",\n type: \"image\",\n props: {\n ...fitImageSize(size, o.maxImageDimension),\n name: file.name,\n isAnimated: mimeType ? isAnimatedImageType(mimeType) : false,\n mimeType,\n src,\n fileSize: file.size,\n },\n meta: {},\n }\n}\n\n/** Build (without storing) an image asset from SVG markup. */\nexport function createImageAssetFromSvgText(text: string, opts: ExternalContentOptions = {}): ImageAsset {\n const o = resolveOptions(opts)\n const size = getSvgTextSize(text) ?? o.fallbackImageSize\n return {\n id: AssetRecordType.createId(),\n typeName: \"asset\",\n type: \"image\",\n props: { ...fitImageSize(size, o.maxImageDimension), name: \"image.svg\", isAnimated: false, mimeType: \"image/svg+xml\", src: svgTextToDataUrl(text) },\n meta: {},\n }\n}\n\nfunction pointOrCenter(editor: Editor, point: VecLike | undefined): VecLike {\n return point ?? editor.getViewportPageCenter()\n}\n\n/** Store `assets` and create one image shape per asset, centered on `point` and stacked by `stackOffset`. */\nexport function createImageShapesForAssets(editor: Editor, assets: readonly ImageAsset[], point: VecLike | undefined, stackOffset = DEFAULT_STACK_OFFSET): ShapeId[] {\n if (assets.length === 0) return []\n const p = pointOrCenter(editor, point)\n const ids: ShapeId[] = []\n editor.run(() => {\n editor.createAssets(assets.map((a) => ({ id: a.id, type: a.type, props: a.props, meta: a.meta })))\n editor.createShapes<ImageShape>(\n assets.map((asset, i) => {\n const id = createShapeId()\n ids.push(id)\n const { w, h } = asset.props\n return {\n id,\n type: \"image\",\n x: p.x - w / 2 + i * stackOffset,\n y: p.y - h / 2 + i * stackOffset,\n props: { w, h, assetId: asset.id },\n }\n }),\n )\n editor.setSelectedShapes(ids)\n })\n return ids\n}\n\n/** Create a text shape whose bounds are centered on `point`. */\nexport function createTextShapeAt(editor: Editor, text: string, point: VecLike | undefined): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.createShape<TextShape>({ id, type: \"text\", x: p.x, y: p.y, props: { text } })\n const shape = editor.getShape<TextShape>(id)\n const bounds = shape ? editor.getShapeGeometry(shape).bounds : undefined\n if (bounds) editor.updateShape<TextShape>({ id, type: \"text\", x: p.x - bounds.w / 2, y: p.y - bounds.h / 2 })\n editor.setSelectedShapes([id])\n })\n return id\n}\n\n/**\n * Install the default drop/paste behaviour: image files become image assets +\n * image shapes, SVG markup becomes an image asset, text and urls become text\n * shapes. Types that already have a handler are left alone. Returns a function\n * that removes the handlers this call installed.\n */\nexport function registerDefaultExternalContentHandlers(editor: Editor, opts: DefaultExternalContentOptions = {}): () => void {\n const o = resolveOptions(opts)\n const disposers: (() => void)[] = []\n\n if (!editor.hasExternalAssetHandler(\"file\")) {\n disposers.push(editor.registerExternalAssetHandler(\"file\", ({ file }) => createImageAssetFromFile(file, o)))\n }\n\n if (!editor.hasExternalContentHandler(\"files\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"files\", async ({ files, point }) => {\n const assets: ImageAsset[] = []\n for (const file of files) {\n const asset = await editor.getAssetForExternalContent({ type: \"file\", file })\n if (asset?.type === \"image\") assets.push(asset)\n }\n if (assets.length === 0) return\n editor.markHistoryStoppingPoint(\"insert images\")\n createImageShapesForAssets(editor, assets, point, o.stackOffset)\n }),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"text\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"text\", ({ text, point }) => {\n const trimmed = text.trim()\n if (!trimmed) return\n editor.markHistoryStoppingPoint(\"insert text\")\n createTextShapeAt(editor, trimmed, point)\n }),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"url\")) {\n // Embed-or-bookmark, not \"a text shape with a url in it\": see\n // `defaultHandleExternalUrlContent`. It degrades to a text shape by itself\n // on an editor that has neither shape registered.\n disposers.push(\n editor.registerExternalContentHandler(\"url\", ({ url, point }) =>\n defaultHandleExternalUrlContent(editor, { url, ...(point ? { point } : {}) }, opts),\n ),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"svg-text\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"svg-text\", ({ text, point }) => {\n editor.markHistoryStoppingPoint(\"insert svg\")\n createImageShapesForAssets(editor, [createImageAssetFromSvgText(text, o)], point, o.stackOffset)\n }),\n )\n }\n\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/** Classify pasted or dropped plain text as our own clipboard JSON (`null`), a url, svg markup, or text. */\nexport function classifyExternalText(text: string, point?: VecLike): ExternalContent | null {\n if (!text.trim()) return null\n if (text.trimStart().startsWith(\"{\")) {\n try {\n const data = JSON.parse(text) as { type?: unknown }\n if (data && data.type === \"application/mocanvas\") return null\n } catch {\n // ordinary text that happens to start with a brace\n }\n }\n const at = point ? { point } : {}\n if (looksLikeSvg(text)) return { type: \"svg-text\", text, ...at }\n if (looksLikeUrl(text)) return { type: \"url\", url: text.trim(), ...at }\n return { type: \"text\", text, ...at }\n}\n","import { createShapeId, type Asset, type Editor, type ShapeId, type VecLike } from \"@mocanvas/editor\"\nimport { EMBED_HEIGHT, EMBED_WIDTH, getEmbedDefinition, type EmbedShape } from \"../shapes/EmbedShapeUtil\"\nimport { BOOKMARK_HEIGHT, BOOKMARK_WIDTH, type BookmarkShape } from \"../shapes/BookmarkShapeUtil\"\nimport { createTextShapeAt } from \"./defaultExternalContentHandlers\"\n\n/**\n * What a pasted or dropped URL turns into.\n *\n * The rule has two halves, and they fail differently on purpose: a url on the\n * embed permit list becomes a live `embed`; anything else becomes a\n * `bookmark` — a link card, unfurled through whatever `url` asset handler the\n * app registered. Refusing to embed an arbitrary page is a security position,\n * not a limitation: it is what keeps a pasted link from putting a third-party\n * iframe on the board.\n */\n\n/** A toast surface, as much of one as this module needs. */\nexport interface ExternalUrlToasts {\n addToast(toast: { id?: string; title?: string; description?: string; severity?: string }): void\n}\n\n/** Options for {@link defaultHandleExternalUrlContent}. */\nexport interface ExternalUrlContentOptions {\n /** Where to report an unfurl that failed. Optional; without one the failure is silent. */\n toasts?: ExternalUrlToasts\n /** Resolve a UI string id for the toast. Defaults to the identity. */\n msg?: (id: string) => string\n}\n\n/** The toast id used when a link could not be unfurled. Stable, so it can be asserted on. */\nexport const BOOKMARK_UNFURL_FAILED = \"bookmark.unfurl-failed\"\n\nfunction pointOrCenter(editor: Editor, point: VecLike | undefined): VecLike {\n return point ?? editor.getViewportPageCenter()\n}\n\n/**\n * Handle a url that arrived from outside the editor.\n *\n * Resolves once the shape exists. It does not reject on an unfurl failure: a\n * link card that says where it points is a better outcome than nothing, so a\n * failed unfurl still creates the bookmark (with no asset) and reports through\n * `opts.toasts`.\n *\n * SEMANTICS-ASSUMED: the third argument. The consumer casts it at every call\n * site (`as unknown as Parameters<typeof …>[2]`), so nothing pins its members;\n * `toasts` and `msg` are the two the consumer's own casts name, and both are\n * optional so the function is usable with no options at all.\n */\nexport async function defaultHandleExternalUrlContent(\n editor: Editor,\n info: { url: string; point?: VecLike },\n opts: ExternalUrlContentOptions = {},\n): Promise<void> {\n const url = info.url.trim()\n if (url.length === 0) return\n\n const match = getEmbedDefinition(url)\n if (match && editor.hasShapeUtil(\"embed\")) {\n createEmbedShape(editor, url, info.point)\n return\n }\n\n // No bookmark shape on a trimmed editor: a link is still worth keeping, and\n // a text shape is the one thing every editor can hold.\n if (!editor.hasShapeUtil(\"bookmark\")) {\n editor.markHistoryStoppingPoint(\"insert url\")\n createTextShapeAt(editor, url, info.point)\n return\n }\n\n let asset: Asset | undefined\n try {\n asset = await editor.getAssetForExternalContent({ type: \"url\", url })\n } catch (error) {\n const msg = opts.msg ?? ((id: string) => id)\n opts.toasts?.addToast({ id: BOOKMARK_UNFURL_FAILED, title: msg(BOOKMARK_UNFURL_FAILED), description: url, severity: \"warning\" })\n asset = undefined\n // Swallowed deliberately — see the doc comment. Kept visible for debugging.\n if (typeof console !== \"undefined\") console.warn(\"[mocanvas] could not unfurl\", url, error)\n }\n\n createBookmarkShape(editor, url, asset, info.point)\n}\n\n/** Create the `embed` shape for an already-matched url, centered on `point`. */\nexport function createEmbedShape(editor: Editor, url: string, point?: VecLike): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.markHistoryStoppingPoint(\"insert embed\")\n editor.createShape<EmbedShape>({\n id,\n type: \"embed\",\n x: p.x - EMBED_WIDTH / 2,\n y: p.y - EMBED_HEIGHT / 2,\n props: { w: EMBED_WIDTH, h: EMBED_HEIGHT, url },\n })\n editor.setSelectedShapes([id])\n })\n return id\n}\n\n/**\n * Create the `bookmark` card for a url, storing `asset` when the unfurl\n * produced one. A card with `assetId: null` is a valid card — it renders the\n * host and the url and nothing else.\n */\nexport function createBookmarkShape(editor: Editor, url: string, asset: Asset | undefined, point?: VecLike): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.markHistoryStoppingPoint(\"insert bookmark\")\n if (asset) editor.createAssets([{ id: asset.id, type: asset.type, props: asset.props, meta: asset.meta }])\n editor.createShape<BookmarkShape>({\n id,\n type: \"bookmark\",\n x: p.x - BOOKMARK_WIDTH / 2,\n y: p.y - BOOKMARK_HEIGHT / 2,\n props: { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, url, assetId: asset ? asset.id : null },\n })\n editor.setSelectedShapes([id])\n })\n return id\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/external/defaultExternalContentHandlers.ts","../src/external/urlContent.ts"],"names":["pointOrCenter","createShapeId"],"mappings":"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","file":"chunk-3F2HKY37.js","sourcesContent":["import { AssetRecordType, createShapeId, type Editor, type ExternalContent, type ImageAsset, type ShapeId, type VecLike } from \"@mocanvas/editor\"\nimport type { ImageShape } from \"../shapes/ImageShapeUtil\"\nimport type { TextShape } from \"../shapes/TextShapeUtil\"\nimport { defaultHandleExternalUrlContent, type ExternalUrlContentOptions } from \"./urlContent\"\n\nexport interface ImageSize {\n w: number\n h: number\n}\n\n/** Resolves an image's pixel size from its source; injectable so tests can run without a DOM. */\nexport type ImageSizeLoader = (src: string, file?: File) => Promise<ImageSize>\n\nexport interface ExternalContentOptions {\n /** Longest side an inserted image may have, in page units. */\n maxImageDimension?: number\n /** Fallback size when an image's dimensions cannot be read. */\n fallbackImageSize?: ImageSize\n loadImageSize?: ImageSizeLoader\n readFileAsDataUrl?: (file: File) => Promise<string>\n /** Offset between shapes when several are inserted at the same point. */\n stackOffset?: number\n}\n\n/** Everything {@link registerDefaultExternalContentHandlers} accepts. */\nexport type DefaultExternalContentOptions = ExternalContentOptions & ExternalUrlContentOptions\n\nexport const DEFAULT_MAX_IMAGE_DIMENSION = 1000\nconst DEFAULT_FALLBACK_IMAGE_SIZE: ImageSize = { w: 100, h: 100 }\nconst DEFAULT_STACK_OFFSET = 20\n\n/** Scale `size` down (never up) so both sides fit within `max`, keeping the aspect ratio. */\nexport function fitImageSize(size: ImageSize, max = DEFAULT_MAX_IMAGE_DIMENSION): ImageSize {\n const w = Math.max(1, size.w)\n const h = Math.max(1, size.h)\n const scale = Math.min(1, max / Math.max(w, h))\n return { w: Math.round(w * scale), h: Math.round(h * scale) }\n}\n\nexport function isImageFile(file: File): boolean {\n return file.type.startsWith(\"image/\")\n}\n\nexport function isSvgFile(file: File): boolean {\n return file.type === \"image/svg+xml\" || /\\.svg$/i.test(file.name)\n}\n\nexport function isAnimatedImageType(mimeType: string): boolean {\n return mimeType === \"image/gif\" || mimeType === \"image/apng\" || mimeType === \"image/webp\"\n}\n\nconst URL_RE = /^(https?:\\/\\/|mailto:)\\S+$/i\n\nexport function looksLikeUrl(text: string): boolean {\n return URL_RE.test(text.trim())\n}\n\nexport function looksLikeSvg(text: string): boolean {\n return /^\\s*(<\\?xml[^>]*>\\s*)?(<!DOCTYPE[^>]*>\\s*)?<svg[\\s>]/i.test(text)\n}\n\n/** Read a file into a `data:` URL, via `FileReader` where present and `arrayBuffer()` elsewhere. */\nexport async function readFileAsDataUrl(file: File): Promise<string> {\n if (typeof FileReader !== \"undefined\") {\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(reader.error ?? new Error(\"Could not read file\"))\n reader.readAsDataURL(file)\n })\n }\n const bytes = new Uint8Array(await file.arrayBuffer())\n let binary = \"\"\n const chunk = 0x8000\n for (let i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode(...bytes.subarray(i, i + chunk))\n return `data:${file.type || \"application/octet-stream\"};base64,${btoa(binary)}`\n}\n\n/** A `data:` URL for an SVG document string. */\nexport function svgTextToDataUrl(text: string): string {\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(text)}`\n}\n\n/** Pixel size declared by an SVG's root `width`/`height` or `viewBox`, or `null` when neither is usable. */\nexport function getSvgTextSize(text: string): ImageSize | null {\n const root = /<svg\\b[^>]*>/i.exec(text)?.[0]\n if (!root) return null\n const attr = (name: string) => new RegExp(`\\\\s${name}\\\\s*=\\\\s*[\"']([^\"']+)[\"']`, \"i\").exec(root)?.[1]\n const px = (v: string | undefined) => {\n if (!v || /%$/.test(v)) return undefined\n const n = parseFloat(v)\n return Number.isFinite(n) && n > 0 ? n : undefined\n }\n const w = px(attr(\"width\"))\n const h = px(attr(\"height\"))\n if (w && h) return { w, h }\n const viewBox = attr(\"viewBox\")\n ?.split(/[\\s,]+/)\n .map(Number)\n if (viewBox && viewBox.length === 4 && viewBox[2]! > 0 && viewBox[3]! > 0) return { w: viewBox[2]!, h: viewBox[3]! }\n return null\n}\n\n/** Default size loader: `createImageBitmap` when available, else an `Image` element. */\nexport const loadImageSizeInBrowser: ImageSizeLoader = async (src, file) => {\n if (file && typeof createImageBitmap === \"function\" && !isSvgFile(file)) {\n try {\n const bitmap = await createImageBitmap(file)\n const size = { w: bitmap.width, h: bitmap.height }\n bitmap.close()\n return size\n } catch {\n // fall through to the Image element (e.g. unsupported type)\n }\n }\n if (typeof Image === \"undefined\") throw new Error(\"No image decoder available\")\n return new Promise<ImageSize>((resolve, reject) => {\n const img = new Image()\n img.onload = () => resolve({ w: img.naturalWidth, h: img.naturalHeight })\n img.onerror = () => reject(new Error(\"Could not decode image\"))\n img.src = src\n })\n}\n\ntype ResolvedOptions = Required<ExternalContentOptions>\n\nfunction resolveOptions(opts: ExternalContentOptions): ResolvedOptions {\n return {\n maxImageDimension: opts.maxImageDimension ?? DEFAULT_MAX_IMAGE_DIMENSION,\n fallbackImageSize: opts.fallbackImageSize ?? DEFAULT_FALLBACK_IMAGE_SIZE,\n loadImageSize: opts.loadImageSize ?? loadImageSizeInBrowser,\n readFileAsDataUrl: opts.readFileAsDataUrl ?? readFileAsDataUrl,\n stackOffset: opts.stackOffset ?? DEFAULT_STACK_OFFSET,\n }\n}\n\n/** Build (without storing) an image asset for a file; `undefined` for non-image files. */\nexport async function createImageAssetFromFile(file: File, opts: ExternalContentOptions = {}): Promise<ImageAsset | undefined> {\n if (!isImageFile(file) && !isSvgFile(file)) return undefined\n const o = resolveOptions(opts)\n const src = await o.readFileAsDataUrl(file)\n let size: ImageSize\n try {\n size = await o.loadImageSize(src, file)\n } catch {\n size = o.fallbackImageSize\n }\n const mimeType = file.type || (isSvgFile(file) ? \"image/svg+xml\" : null)\n return {\n id: AssetRecordType.createId(),\n typeName: \"asset\",\n type: \"image\",\n props: {\n ...fitImageSize(size, o.maxImageDimension),\n name: file.name,\n isAnimated: mimeType ? isAnimatedImageType(mimeType) : false,\n mimeType,\n src,\n fileSize: file.size,\n },\n meta: {},\n }\n}\n\n/** Build (without storing) an image asset from SVG markup. */\nexport function createImageAssetFromSvgText(text: string, opts: ExternalContentOptions = {}): ImageAsset {\n const o = resolveOptions(opts)\n const size = getSvgTextSize(text) ?? o.fallbackImageSize\n return {\n id: AssetRecordType.createId(),\n typeName: \"asset\",\n type: \"image\",\n props: { ...fitImageSize(size, o.maxImageDimension), name: \"image.svg\", isAnimated: false, mimeType: \"image/svg+xml\", src: svgTextToDataUrl(text) },\n meta: {},\n }\n}\n\nfunction pointOrCenter(editor: Editor, point: VecLike | undefined): VecLike {\n return point ?? editor.getViewportPageCenter()\n}\n\n/** Store `assets` and create one image shape per asset, centered on `point` and stacked by `stackOffset`. */\nexport function createImageShapesForAssets(editor: Editor, assets: readonly ImageAsset[], point: VecLike | undefined, stackOffset = DEFAULT_STACK_OFFSET): ShapeId[] {\n if (assets.length === 0) return []\n const p = pointOrCenter(editor, point)\n const ids: ShapeId[] = []\n editor.run(() => {\n editor.createAssets(assets.map((a) => ({ id: a.id, type: a.type, props: a.props, meta: a.meta })))\n editor.createShapes<ImageShape>(\n assets.map((asset, i) => {\n const id = createShapeId()\n ids.push(id)\n const { w, h } = asset.props\n return {\n id,\n type: \"image\",\n x: p.x - w / 2 + i * stackOffset,\n y: p.y - h / 2 + i * stackOffset,\n props: { w, h, assetId: asset.id },\n }\n }),\n )\n editor.setSelectedShapes(ids)\n })\n return ids\n}\n\n/** Create a text shape whose bounds are centered on `point`. */\nexport function createTextShapeAt(editor: Editor, text: string, point: VecLike | undefined): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.createShape<TextShape>({ id, type: \"text\", x: p.x, y: p.y, props: { text } })\n const shape = editor.getShape<TextShape>(id)\n const bounds = shape ? editor.getShapeGeometry(shape).bounds : undefined\n if (bounds) editor.updateShape<TextShape>({ id, type: \"text\", x: p.x - bounds.w / 2, y: p.y - bounds.h / 2 })\n editor.setSelectedShapes([id])\n })\n return id\n}\n\n/**\n * Install the default drop/paste behaviour: image files become image assets +\n * image shapes, SVG markup becomes an image asset, text and urls become text\n * shapes. Types that already have a handler are left alone. Returns a function\n * that removes the handlers this call installed.\n */\nexport function registerDefaultExternalContentHandlers(editor: Editor, opts: DefaultExternalContentOptions = {}): () => void {\n const o = resolveOptions(opts)\n const disposers: (() => void)[] = []\n\n if (!editor.hasExternalAssetHandler(\"file\")) {\n disposers.push(editor.registerExternalAssetHandler(\"file\", ({ file }) => createImageAssetFromFile(file, o)))\n }\n\n if (!editor.hasExternalContentHandler(\"files\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"files\", async ({ files, point }) => {\n const assets: ImageAsset[] = []\n for (const file of files) {\n const asset = await editor.getAssetForExternalContent({ type: \"file\", file })\n if (asset?.type === \"image\") assets.push(asset)\n }\n if (assets.length === 0) return\n editor.markHistoryStoppingPoint(\"insert images\")\n createImageShapesForAssets(editor, assets, point, o.stackOffset)\n }),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"text\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"text\", ({ text, point }) => {\n const trimmed = text.trim()\n if (!trimmed) return\n editor.markHistoryStoppingPoint(\"insert text\")\n createTextShapeAt(editor, trimmed, point)\n }),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"url\")) {\n // Embed-or-bookmark, not \"a text shape with a url in it\": see\n // `defaultHandleExternalUrlContent`. It degrades to a text shape by itself\n // on an editor that has neither shape registered.\n disposers.push(\n editor.registerExternalContentHandler(\"url\", ({ url, point }) =>\n defaultHandleExternalUrlContent(editor, { url, ...(point ? { point } : {}) }, opts),\n ),\n )\n }\n\n if (!editor.hasExternalContentHandler(\"svg-text\")) {\n disposers.push(\n editor.registerExternalContentHandler(\"svg-text\", ({ text, point }) => {\n editor.markHistoryStoppingPoint(\"insert svg\")\n createImageShapesForAssets(editor, [createImageAssetFromSvgText(text, o)], point, o.stackOffset)\n }),\n )\n }\n\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/** Classify pasted or dropped plain text as our own clipboard JSON (`null`), a url, svg markup, or text. */\nexport function classifyExternalText(text: string, point?: VecLike): ExternalContent | null {\n if (!text.trim()) return null\n if (text.trimStart().startsWith(\"{\")) {\n try {\n const data = JSON.parse(text) as { type?: unknown }\n if (data && data.type === \"application/mocanvas\") return null\n } catch {\n // ordinary text that happens to start with a brace\n }\n }\n const at = point ? { point } : {}\n if (looksLikeSvg(text)) return { type: \"svg-text\", text, ...at }\n if (looksLikeUrl(text)) return { type: \"url\", url: text.trim(), ...at }\n return { type: \"text\", text, ...at }\n}\n","import { createShapeId, type Asset, type Editor, type ShapeId, type VecLike } from \"@mocanvas/editor\"\nimport { EMBED_HEIGHT, EMBED_WIDTH, getEmbedDefinition, type EmbedShape } from \"../shapes/EmbedShapeUtil\"\nimport { BOOKMARK_HEIGHT, BOOKMARK_WIDTH, type BookmarkShape } from \"../shapes/BookmarkShapeUtil\"\nimport { createTextShapeAt } from \"./defaultExternalContentHandlers\"\n\n/**\n * What a pasted or dropped URL turns into.\n *\n * The rule has two halves, and they fail differently on purpose: a url on the\n * embed permit list becomes a live `embed`; anything else becomes a\n * `bookmark` — a link card, unfurled through whatever `url` asset handler the\n * app registered. Refusing to embed an arbitrary page is a security position,\n * not a limitation: it is what keeps a pasted link from putting a third-party\n * iframe on the board.\n */\n\n/** A toast surface, as much of one as this module needs. */\nexport interface ExternalUrlToasts {\n addToast(toast: { id?: string; title?: string; description?: string; severity?: string }): void\n}\n\n/** Options for {@link defaultHandleExternalUrlContent}. */\nexport interface ExternalUrlContentOptions {\n /** Where to report an unfurl that failed. Optional; without one the failure is silent. */\n toasts?: ExternalUrlToasts\n /** Resolve a UI string id for the toast. Defaults to the identity. */\n msg?: (id: string) => string\n}\n\n/** The toast id used when a link could not be unfurled. Stable, so it can be asserted on. */\nexport const BOOKMARK_UNFURL_FAILED = \"bookmark.unfurl-failed\"\n\nfunction pointOrCenter(editor: Editor, point: VecLike | undefined): VecLike {\n return point ?? editor.getViewportPageCenter()\n}\n\n/**\n * Handle a url that arrived from outside the editor.\n *\n * Resolves once the shape exists. It does not reject on an unfurl failure: a\n * link card that says where it points is a better outcome than nothing, so a\n * failed unfurl still creates the bookmark (with no asset) and reports through\n * `opts.toasts`.\n *\n * SEMANTICS-ASSUMED: the third argument. The consumer casts it at every call\n * site (`as unknown as Parameters<typeof …>[2]`), so nothing pins its members;\n * `toasts` and `msg` are the two the consumer's own casts name, and both are\n * optional so the function is usable with no options at all.\n */\nexport async function defaultHandleExternalUrlContent(\n editor: Editor,\n info: { url: string; point?: VecLike },\n opts: ExternalUrlContentOptions = {},\n): Promise<void> {\n const url = info.url.trim()\n if (url.length === 0) return\n\n const match = getEmbedDefinition(url)\n if (match && editor.hasShapeUtil(\"embed\")) {\n createEmbedShape(editor, url, info.point)\n return\n }\n\n // No bookmark shape on a trimmed editor: a link is still worth keeping, and\n // a text shape is the one thing every editor can hold.\n if (!editor.hasShapeUtil(\"bookmark\")) {\n editor.markHistoryStoppingPoint(\"insert url\")\n createTextShapeAt(editor, url, info.point)\n return\n }\n\n let asset: Asset | undefined\n try {\n asset = await editor.getAssetForExternalContent({ type: \"url\", url })\n } catch (error) {\n const msg = opts.msg ?? ((id: string) => id)\n opts.toasts?.addToast({ id: BOOKMARK_UNFURL_FAILED, title: msg(BOOKMARK_UNFURL_FAILED), description: url, severity: \"warning\" })\n asset = undefined\n // Swallowed deliberately — see the doc comment. Kept visible for debugging.\n if (typeof console !== \"undefined\") console.warn(\"[mocanvas] could not unfurl\", url, error)\n }\n\n createBookmarkShape(editor, url, asset, info.point)\n}\n\n/** Create the `embed` shape for an already-matched url, centered on `point`. */\nexport function createEmbedShape(editor: Editor, url: string, point?: VecLike): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.markHistoryStoppingPoint(\"insert embed\")\n editor.createShape<EmbedShape>({\n id,\n type: \"embed\",\n x: p.x - EMBED_WIDTH / 2,\n y: p.y - EMBED_HEIGHT / 2,\n props: { w: EMBED_WIDTH, h: EMBED_HEIGHT, url },\n })\n editor.setSelectedShapes([id])\n })\n return id\n}\n\n/**\n * Create the `bookmark` card for a url, storing `asset` when the unfurl\n * produced one. A card with `assetId: null` is a valid card — it renders the\n * host and the url and nothing else.\n */\nexport function createBookmarkShape(editor: Editor, url: string, asset: Asset | undefined, point?: VecLike): ShapeId {\n const p = pointOrCenter(editor, point)\n const id = createShapeId()\n editor.run(() => {\n editor.markHistoryStoppingPoint(\"insert bookmark\")\n if (asset) editor.createAssets([{ id: asset.id, type: asset.type, props: asset.props, meta: asset.meta }])\n editor.createShape<BookmarkShape>({\n id,\n type: \"bookmark\",\n x: p.x - BOOKMARK_WIDTH / 2,\n y: p.y - BOOKMARK_HEIGHT / 2,\n props: { w: BOOKMARK_WIDTH, h: BOOKMARK_HEIGHT, url, assetId: asset ? asset.id : null },\n })\n editor.setSelectedShapes([id])\n })\n return id\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tipTapDefaultExtensions, richTextToHtml, isRichText, richTextToText, getRichTextEditorFactory, applyPlainTextToRichText, propsOf, readNumber, readText, readRichText, readStyle, readEnum, readPoint, arrowShapeMigrations, arrowShapeProps, toRichText, svgPath, frameShapeProps, frameShapeMigrations, readString, rectPath, readBoolean, geoShapeProps, noteShapeProps, noteShapeMigrations, richTextToBlocks, textShapeProps, textShapeMigrations, videoShapeProps, videoShapeMigrations, safeHref, getBookmarkCard, getBookmarkLayout, BOOKMARK_STROKE_WIDTH, BOOKMARK_STROKE, BOOKMARK_FILL, BOOKMARK_RADIUS, BOOKMARK_BANNER_FILL, BOOKMARK_TITLE_FONT_SIZE, BOOKMARK_TITLE_COLOR, BOOKMARK_META_COLOR, BOOKMARK_TEXT_FONT_SIZE, BOOKMARK_TEXT_COLOR, BOOKMARK_META_FONT_SIZE, getEmbedDefinition, EMBED_PLACEHOLDER_STROKE, EMBED_PLACEHOLDER_FILL, EMBED_RADIUS, EMBED_PLACEHOLDER_PADDING, EMBED_PLACEHOLDER_FONT_SIZE, EMBED_PLACEHOLDER_TEXT } from './chunk-
|
|
1
|
+
import { tipTapDefaultExtensions, richTextToHtml, isRichText, richTextToText, getRichTextEditorFactory, applyPlainTextToRichText, propsOf, readNumber, readText, readRichText, readStyle, readEnum, readPoint, arrowShapeMigrations, arrowShapeProps, toRichText, svgPath, frameShapeProps, frameShapeMigrations, readString, rectPath, readBoolean, geoShapeProps, noteShapeProps, noteShapeMigrations, richTextToBlocks, textShapeProps, textShapeMigrations, videoShapeProps, videoShapeMigrations, safeHref, getBookmarkCard, getBookmarkLayout, BOOKMARK_STROKE_WIDTH, BOOKMARK_STROKE, BOOKMARK_FILL, BOOKMARK_RADIUS, BOOKMARK_BANNER_FILL, BOOKMARK_TITLE_FONT_SIZE, BOOKMARK_TITLE_COLOR, BOOKMARK_META_COLOR, BOOKMARK_TEXT_FONT_SIZE, BOOKMARK_TEXT_COLOR, BOOKMARK_META_FONT_SIZE, getEmbedDefinition, EMBED_PLACEHOLDER_STROKE, EMBED_PLACEHOLDER_FILL, EMBED_RADIUS, EMBED_PLACEHOLDER_PADDING, EMBED_PLACEHOLDER_FONT_SIZE, EMBED_PLACEHOLDER_TEXT } from './chunk-S7FPGQAA.js';
|
|
2
2
|
import { getGeoGeometry, arcToCubicSegments, pathWordsToSvgD } from './chunk-OMUOD53T.js';
|
|
3
3
|
import { DEFAULT_THEME, GEO_SHAPE_KINDS, createBuiltInShapePropsMigrationIds, createShapePropsMigrationSequence, ARROW_SHAPE_KINDS, Vec, Polyline2d, CubicSpline2d, Polygon2d, Box, Group2d, STROKE_SIZES, hexToRgba, getColorValue, DEFAULT_FILL_TOKENS, DefaultFontFaces, useEditor, DefaultFontStyle, ARROWHEAD_KINDS, DefaultSizeStyle, DefaultDashStyle, DefaultFillStyle, DefaultLabelColorStyle, DefaultColorStyle, getDefaultDisplayValues, ShapeUtil, FONT_SIZES, Rectangle2d, getDisplayValues, BaseFrameLikeShapeUtil, DefaultVerticalAlignStyle, DefaultHorizontalAlignStyle, GeoShapeGeoStyle, BaseBoxShapeUtil, GEO_KIND, LIGHT_THEME, PATH_OP } from '@mocanvas/editor';
|
|
4
4
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
@@ -3366,5 +3366,5 @@ async function copyBlobToClipboard(blob) {
|
|
|
3366
3366
|
}
|
|
3367
3367
|
|
|
3368
3368
|
export { ARROW_KINDS, ARROW_LABEL_PADDING, ARROW_TERMINAL_GAP_STROKES, AVG_CHAR_WIDTH, ArrowShapeUtil, DEFAULT_GEO_TYPE_DEFINITIONS, ELBOW_CORNER_STROKES, FALLBACK_THEME_COLORS, FRAME_FILL, FRAME_NAME_COLOR, FRAME_NAME_FONT_SIZE, FRAME_NAME_GAP, FRAME_NAME_HEIGHT, FRAME_NAME_OFFSET, FRAME_STROKE, FRAME_STROKE_WIDTH, FrameShapeUtil, GEO_DEFAULT_SIZE, GEO_LABEL_PADDING, GeoShapeUtil, LINE_HEIGHT, MAX_TEXT_TEXTURE_PX, NOTE_GRADIENT_TOP_SCALE, NOTE_PADDING, NOTE_SHADOW_BLUR, NOTE_SHADOW_COLOR, NOTE_SHADOW_OFFSET_Y, NOTE_SHADOW_OPACITY, NOTE_SHADOW_SPREAD, NOTE_SIZE, NoteShapeUtil, RICH_TEXT_BLOCK_CSS, SVG_DARK_BACKGROUND, SVG_EXPORT_DEFAULT_PADDING, SVG_LIGHT_BACKGROUND, TEXT_SHAPE_MIN_WIDTH, TextLabel, TextMeasure, TextShapeUtil, VIDEO_HEIGHT, VIDEO_PLACEHOLDER_FILL, VIDEO_PLACEHOLDER_STROKE, VIDEO_PLAY_COLOR, VIDEO_PLAY_SIZE, VIDEO_TIME_EPSILON, VIDEO_WIDTH, VideoShapeUtil, alignToJustify, alignToTextAlign, applyTransform, bodyToGeometry, computeGrowY, copyBlobToClipboard, dashArray, defaultShapeSvgRenderer, downloadBlob, escapeXml, estimateTextSize, exportToBlob, geoShapeMigrations, geoShapeVersions, geometryFallbackSvg, geometryToSvgPaths, getAnchorInShapeSpace, getArrowBindingTargetAtPoint, getArrowBindings, getArrowBody, getArrowDisplayValues, getArrowTerminalGap, getArrowTerminalsInArrowSpace, getArrowheadGeometry, getArrowheadInset, getArrowheadLength, getBendFromPoint, getBodyLength, getBoundElbowAxes, getDashId, getDominantAxis, getElbowBody, getElbowMidPointFromPoint, getElbowRoute, getExportBounds, getExportShapes, getFillRgba, getFontFamily, getFrameDisplayValues, getGeoDisplayValues, getGeoGrowY, getGeoTypeDefinition, getLabelFontFaces, getLabelOpticalLift, getNormalizedAnchor, getNoteBodyGradientCss, getNoteDisplayValues, getNoteFillCssColor, getNoteFillRgba, getNoteFontSize, getNoteGradientTopCssColor, getNoteGradientTopFrom, getNoteGrowY, getNoteShadowCss, getNoteShadowSvgRect, getNoteTextCssColor, getOutlineSegments, getPointOnBody, getRichTextExtensions, getStrokeRgba, getSvgString, getTangentOnBody, getTextCssColor, getTextDisplayValues, getTextMeasure, getTextShapeBox, getTextShapeHeight, getTextShapeSize, getTextShapeSizeFor, getTextShapeTextureSpec, getTextTextureKey, getTextTextureScale, getTheme, getThemeColors, getVideoDisplayValues, getVideoPlayTriangle, getVideoSource, hexToCssRgba, horizontalAlignToFlex, horizontalAlignToTextAlign, intersectSegments, isVideoAutoplayAllowed, labelFontFamily, labelFontStyle, labelGapOnBody, labelTextAlign, matrixAttr, measureGeoLabel, measureLabel, measureRichText, readArrowProps, readArrowShape, readGeoProps, readNoteProps, readTextProps, registerShapeSvgRenderer, renderHtmlFromRichTextForMeasurement, renderHtmlFromRichTextWithExtensions, renderTextToCanvas, rgbaToHex, roundElbowCorners, shapeSvgRenderers, shapeToBackgroundSvg, shapeToSvg, shortenBody, svgResultToMarkup, textTextureAlign, textTextureText, textToSvg, toDisplayText, trimTrailingWhitespace, verticalAlignToAlignItems, verticalAlignToFlex, wrapTextLines, wrapTextTextureLines, wrapTextTextureRuns };
|
|
3369
|
-
//# sourceMappingURL=chunk-
|
|
3370
|
-
//# sourceMappingURL=chunk-
|
|
3369
|
+
//# sourceMappingURL=chunk-HRUFJLK5.js.map
|
|
3370
|
+
//# sourceMappingURL=chunk-HRUFJLK5.js.map
|