@mocanvas/mocanvas 4.0.2 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ARCHITECTURE.md CHANGED
@@ -18,9 +18,15 @@ document model, tools, and UI live in TypeScript.
18
18
 
19
19
  ## Non-goals (v1)
20
20
 
21
- - Wire compatibility with tldraw's sync protocol.
22
- - Slot-compatible UI components.
21
+ - Wire compatibility with tldraw's sync protocol. `@mocanvas/sync` is a
22
+ transport of its own; it does not speak tldraw's wire format.
23
23
  - Pixel-identical rendering of every built-in shape.
24
+ - Shipping message catalogues. The translation seam exists
25
+ (`TLUiOverrides.translations`); the strings are the host's.
26
+
27
+ Slot-compatible UI components were a non-goal here and are no longer: the
28
+ `TLComponents` and `TLUiOverrides` surfaces are implemented. See
29
+ [MIGRATION.md §7](MIGRATION.md).
24
30
 
25
31
  ## Clean-room rule
26
32
 
package/MIGRATION.md CHANGED
@@ -10,7 +10,8 @@ The short version: your **documents, records, ids and `.tldr` files carry over
10
10
  untouched**, most of your **`Editor` calls carry over untouched**, your
11
11
  **`ShapeUtil` / `StateNode` / `BindingUtil` subclasses keep their shape**, and
12
12
  the real work is (a) imports, (b) teaching custom shapes about the GPU
13
- renderer, and (c) rebuilding UI, which has no slot compatibility yet.
13
+ renderer, and (c) UI, where the slots are compatible but the message
14
+ catalogues are yours to supply (§7).
14
15
 
15
16
  ---
16
17
 
@@ -237,6 +238,21 @@ the `export * from "@mocanvas/mocanvas"` at the top of the package.
237
238
  Everything `@mocanvas/editor` exports is re-exported by `@mocanvas/mocanvas`, so in app
238
239
  code you can import from `@mocanvas/mocanvas` alone.
239
240
 
241
+ ### Step 1b — the stylesheet
242
+
243
+ | Old import | New import |
244
+ | ------------------------ | --------------------------------- |
245
+ | `"tldraw/tldraw.css"` | `"@mocanvas/mocanvas/mocanvas.css"` |
246
+
247
+ Same arrangement, same one line, wherever you had it. The default UI is
248
+ unstyled without it.
249
+
250
+ `@mocanvas/compat` does not re-export the stylesheet — a package can only
251
+ export files it contains — so this import names `@mocanvas/mocanvas` even
252
+ during the zero-rename step. Under pnpm's strict `node_modules` that means
253
+ adding `@mocanvas/mocanvas` to your own dependencies alongside
254
+ `@mocanvas/compat`.
255
+
240
256
  ### Step 2 — drop the prefixes
241
257
 
242
258
  Once the app builds and runs against `@mocanvas/compat`, rename `TLFoo` →
@@ -499,26 +515,35 @@ returns both and `putContentOntoCurrentPage` accepts both.
499
515
 
500
516
  ## 7. UI
501
517
 
502
- **There is no slot compatibility yet**, and it is an explicit v1 non-goal. Do
503
- not expect your existing overrides of tldraw's UI components to compile.
518
+ **Slot compatibility exists.** This section used to say it did not and that it
519
+ was a v1 non-goal; both stopped being true before 4.0. Your existing overrides
520
+ of tldraw's UI components are the shape mocanvas expects.
504
521
 
505
522
  What you have:
506
523
 
507
- - `hideUi` on `<Mocanvas />` turns off the default toolbar and zoom bar
508
- entirely, leaving you the canvas and your `children`.
509
- - `components` (passed through to `<Canvas>`) lets you replace the canvas-level
510
- render slots.
524
+ - `components` the `TLComponents` map, at both the canvas level and the
525
+ chrome level. Pass a component to replace a slot, `null` to remove it.
526
+ - `overrides` `TLUiOverrides`, with `tools`, `actions` and `translations`.
527
+ The tool and action lists are what the toolbar, the menus and the keyboard
528
+ bindings are all built from, so rewriting an entry changes all three
529
+ together rather than only what is drawn.
530
+ - The default chrome as named exports — `TldrawUi`, `DefaultUi`,
531
+ `DefaultToolbar`, `DefaultMainMenu` and the rest — so you can render one
532
+ piece of it inside chrome of your own.
533
+ - `useTools()` and `useActions()` return those lists after overrides, which is
534
+ what a toolbar of your own should render from.
535
+ - `hideUi` on `<Mocanvas />` still turns the chrome off entirely, leaving you
536
+ the canvas and your `children`.
511
537
  - `useEditor()` inside any descendant of `<Canvas>` / `<EditorProvider>`
512
538
  returns the `Editor` (`useMaybeEditor()` returns `Editor | null`).
513
539
  - `track(Component)` and `useValue` from `@mocanvas/state/react` — re-exported
514
- from `@mocanvas/editor` and `@mocanvas/mocanvas` — make a component re-render when the
515
- signals it reads change.
540
+ from `@mocanvas/editor` and `@mocanvas/mocanvas` — make a component re-render
541
+ when the signals it reads change.
516
542
 
517
- The practical migration is: `hideUi`, then rebuild your chrome as ordinary
518
- React inside `<Mocanvas>`, reading and driving the editor through `useEditor`
519
- and `track`. Because the default UI is still being reworked, treat its internals
520
- as unstable and build against `useEditor` rather than against specific UI
521
- components.
543
+ One real difference remains: mocanvas ships **no message catalogues**, so
544
+ `overrides.translations` is where UI strings come from. The language menu lists
545
+ only the locales you supply a non-empty dictionary for, and renders nothing at
546
+ all if you supply none see the note in `UI.md`.
522
547
 
523
548
  ---
524
549
 
@@ -626,19 +651,20 @@ before.
626
651
 
627
652
  ## 10. Known gaps
628
653
 
629
- Taken from the "phase 3" and "later" rows of [COMPAT.md](COMPAT.md), plus what
630
- the code confirms today.
654
+ Every row below was re-checked against the built package rather than carried
655
+ forward from the roadmap. Rows that used to sit here and no longer belong —
656
+ `editor.resizeShape` and `stretchShapes`, `editor.getSvgString` and `toImage`,
657
+ `editor.textMeasure`/`user`/`menus`, presence records, and slot-compatible UI —
658
+ are all present, and the entries claiming otherwise were stale rather than
659
+ aspirational.
631
660
 
632
661
  | Gap | Status |
633
662
  | --- | ------ |
634
- | `editor.resizeShape`, `editor.stretchShapes` | phase 3. Interactive resize lives in the select tool; `ShapeUtil.onResize` and `BaseBoxShapeUtil` work, but there is no imperative resize entry point on `Editor`. |
635
- | `editor.getSvgString`, `editor.toImage` | Not `Editor` methods. Export is a set of free functions in `@mocanvas/mocanvas`: `getSvgString(editor, ids?, opts?)`, `exportToBlob(editor, opts)`, `downloadBlob(blob, filename)`, `copyBlobToClipboard(blob)`. |
636
663
  | `ShapeUtil.toSvg`, `ShapeUtil.toBackgroundSvg` | Not `ShapeUtil` members. Custom shapes contribute to SVG export through `registerShapeSvgRenderer(type, renderer)`; without one they fall back to `geometryFallbackSvg`. |
637
- | `pointer` and `instance_presence` records | later collaboration. No presence records, and no `mergeRemoteChanges` transport yet. |
638
- | Sync protocol | Explicit v1 non-goal. Wire compatibility with tldraw's sync protocol is not planned for v1. |
639
- | Slot-compatible UI | Explicit v1 non-goal. See §7. |
664
+ | Sync protocol | Wire compatibility with tldraw's own sync protocol is not planned. `@mocanvas/sync` is a working transport of its own — presence records, `store.mergeRemoteChanges`, and a relay but it does not speak tldraw's wire format. |
640
665
  | `image` shape on the GPU | The texture path exists in the engine (`StyleWords.texture` + `uploadTexture`) but the `image` shape still draws an `<img>` in the DOM overlay. |
641
- | `editor.textMeasure`, `editor.user`, `editor.menus` | Not implemented. Text measurement is DOM-backed inside the text layer; `editor.inputs` and `editor.sideEffects` do exist. |
666
+ | `FrameShapeUtil` default size | mocanvas creates a frame at **160×90**; tldraw creates one at 320×180. Same aspect, half the size. It only bites code that creates a frame *programmatically* without passing `w`/`h` drawing one with the tool sizes it from the drag either way. Pass explicit dimensions if the size matters to you. Aligning the default is a behaviour change and is not being made in a patch release. |
667
+ | An unregistered shape type in `store.put()` | Accepted, deliberately, and confirmed rather than fixed. A `.tldr` written by a build that knows a shape type this one does not must survive a load/save round trip instead of being dropped on the next save, so an unknown `type` passes its props through untouched. Its *other* fields are still validated — `x` must be a number whatever the type is — and a type you did register is validated in full. See `COMPAT.md`. |
642
668
  | Text rendering | DOM overlay. Glyph-atlas text in WASM is phase 3. |
643
669
  | GPU frame clipping | The `CLIP` flag and `isClipShape` hook are wired end to end, but no built-in shape enables it yet (phase 3). |
644
670
  | WebGPU backend | Phase 3. WebGL2 is the only backend today, behind `RenderBackend`. |
package/README.md CHANGED
@@ -112,11 +112,32 @@ npm install @mocanvas/mocanvas react react-dom
112
112
 
113
113
  `react` and `react-dom` (>= 18) are peer dependencies.
114
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
+
115
135
  ## Use
116
136
 
117
137
  ```tsx
118
138
  import { createRoot } from "react-dom/client"
119
139
  import { Mocanvas, createShapeId, type Editor } from "@mocanvas/mocanvas"
140
+ import "@mocanvas/mocanvas/mocanvas.css"
120
141
 
121
142
  function App() {
122
143
  function onMount(editor: Editor) {
@@ -142,8 +163,12 @@ function App() {
142
163
  createRoot(document.getElementById("root")!).render(<App />)
143
164
  ```
144
165
 
145
- `<Mocanvas />` fills its container, so give the container a size. The default
146
- UI stylesheet is imported by the package itself — no extra CSS import.
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.
147
172
 
148
173
  ## ESM only
149
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. `ui.css` still
126
- loads, so the canvas chrome tokens above keep working. Default keyboard
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-OCYJAMXT.js';
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-TWCVRVK2.js.map
296
- //# sourceMappingURL=chunk-TWCVRVK2.js.map
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-OCYJAMXT.js';
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-DMCI6V5Y.js.map
3370
- //# sourceMappingURL=chunk-DMCI6V5Y.js.map
3369
+ //# sourceMappingURL=chunk-HRUFJLK5.js.map
3370
+ //# sourceMappingURL=chunk-HRUFJLK5.js.map