@inkandswitch/patchwork-pkg-base 0.0.118 → 0.0.119

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.
@@ -1 +1 @@
1
- automerge:45bZCda3UDvGWVY3mzVXtF6pH7VY
1
+ automerge:2RX2vv2kin4WYv8B6Tf6r75tcMo9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.118",
3
+ "version": "0.0.119",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -26,6 +26,16 @@ var embedTheme = EditorView.baseTheme({
26
26
  "&dark .cm-embed": {
27
27
  borderColor: "#333"
28
28
  },
29
+ // Images are just images: no border, natural size, never wider than the text.
30
+ "&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image": {
31
+ border: "none",
32
+ borderRadius: "0"
33
+ },
34
+ ".cm-embed-image > img": {
35
+ display: "block",
36
+ maxWidth: "100%",
37
+ height: "auto"
38
+ },
29
39
  ".cm-embed > patchwork-view": {
30
40
  display: "block",
31
41
  height: "500px",
@@ -51,6 +61,23 @@ var EmbedWidget = class extends WidgetType {
51
61
  toDOM(view) {
52
62
  const container = document.createElement("div");
53
63
  container.className = "cm-embed";
64
+ if (this.toolId) {
65
+ this.mountFrame(view, container);
66
+ } else {
67
+ const cleanup = mountImageOrFrame(
68
+ this.docId,
69
+ container,
70
+ () => this.mountFrame(view, container)
71
+ );
72
+ cleanups.set(container, cleanup);
73
+ }
74
+ return container;
75
+ }
76
+ destroy(dom) {
77
+ cleanups.get(dom)?.();
78
+ cleanups.delete(dom);
79
+ }
80
+ mountFrame(view, container) {
54
81
  const patchworkView = document.createElement("patchwork-view");
55
82
  patchworkView.setAttribute("doc-url", `automerge:${this.docId}`);
56
83
  patchworkView.setAttribute("tool-id", "embed");
@@ -64,7 +91,6 @@ var EmbedWidget = class extends WidgetType {
64
91
  if (toolId) this.setTool(view, container, toolId);
65
92
  });
66
93
  container.appendChild(patchworkView);
67
- return container;
68
94
  }
69
95
  setTool(view, container, toolId) {
70
96
  const from = view.posAtDOM(container);
@@ -78,6 +104,58 @@ var EmbedWidget = class extends WidgetType {
78
104
  return true;
79
105
  }
80
106
  };
107
+ var cleanups = /* @__PURE__ */ new WeakMap();
108
+ var IMAGE_EXTENSIONS = ["svg", "png", "jpg", "jpeg", "gif", "webp", "bmp"];
109
+ function isImageDoc(doc) {
110
+ if (doc?.["@patchwork"]?.type !== "file") return false;
111
+ if (!(doc.content instanceof Uint8Array)) return false;
112
+ return !!doc.mimeType?.startsWith("image/") || IMAGE_EXTENSIONS.includes(doc.extension?.toLowerCase() ?? "");
113
+ }
114
+ function mountImageOrFrame(docId, container, mountFrame) {
115
+ const repo = window.repo;
116
+ if (!repo?.find) {
117
+ mountFrame();
118
+ return () => {
119
+ };
120
+ }
121
+ let disposed = false;
122
+ let objectUrl;
123
+ let handle;
124
+ let img;
125
+ const render = () => {
126
+ const doc = handle.doc();
127
+ if (!isImageDoc(doc)) return;
128
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
129
+ objectUrl = URL.createObjectURL(
130
+ new Blob([doc.content], { type: doc.mimeType || "" })
131
+ );
132
+ img.src = objectUrl;
133
+ img.alt = doc.name ?? "";
134
+ };
135
+ Promise.resolve(repo.find(`automerge:${docId}`)).then(
136
+ (h) => {
137
+ if (disposed) return;
138
+ handle = h;
139
+ if (!isImageDoc(handle.doc())) {
140
+ mountFrame();
141
+ return;
142
+ }
143
+ container.classList.add("cm-embed-image");
144
+ img = document.createElement("img");
145
+ container.appendChild(img);
146
+ render();
147
+ handle.on("change", render);
148
+ },
149
+ () => {
150
+ if (!disposed) mountFrame();
151
+ }
152
+ );
153
+ return () => {
154
+ disposed = true;
155
+ handle?.off?.("change", render);
156
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
157
+ };
158
+ }
81
159
  var EMBED_PATTERN = /\[patchwork:([^/\]]+)(?:\/([^\]]+))?\]/g;
82
160
  function getEmbedLinks(view) {
83
161
  const widgets = [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/extensions/embed.ts", "../../src/themes/embed.ts"],
4
- "sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n dropCursor,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport { Range } from \"@codemirror/state\";\nimport {\n type AutomergeUrl,\n type DocumentId,\n isValidDocumentId,\n parseAutomergeUrl,\n} from \"@automerge/automerge-repo/slim\";\nimport { embedTheme } from \"../themes/embed.ts\";\n\n/**\n * Renders an embedded doc through the shared \"embed\" tool, which draws the\n * chrome and nests the content view. The inner tool travels via the\n * `embed-tool-id` attribute; tool picks come back as\n * `patchwork:embed-tool-changed` events and are persisted by rewriting the\n * marker (which recreates the widget).\n */\nclass EmbedWidget extends WidgetType {\n readonly docId: DocumentId;\n // `null` = no pinned tool; the embed frame uses the datatype's fallback.\n readonly toolId: string | null;\n readonly embedText: string;\n\n constructor(docId: DocumentId, toolId: string | null, embedText: string) {\n super();\n this.docId = docId;\n this.toolId = toolId;\n this.embedText = embedText;\n }\n\n eq(other: EmbedWidget) {\n return other.docId === this.docId && other.toolId === this.toolId;\n }\n\n toDOM(view: EditorView) {\n const container = document.createElement(\"div\");\n container.className = \"cm-embed\";\n\n const patchworkView = document.createElement(\"patchwork-view\");\n // No heads in the url: the embed follows whatever version the host\n // resolves \u2014 the active checkpoint when one is checked out, live otherwise.\n patchworkView.setAttribute(\"doc-url\", `automerge:${this.docId}`);\n patchworkView.setAttribute(\"tool-id\", \"embed\");\n if (this.toolId) patchworkView.setAttribute(\"embed-tool-id\", this.toolId);\n // Explicit inline height: without it the element collapses to 0px (the\n // stylesheet rule isn't reliably applied here).\n patchworkView.style.display = \"block\";\n patchworkView.style.height = \"500px\";\n patchworkView.style.width = \"100%\";\n\n // Persist tool picks into the marker text. Stop propagation so nested\n // embeds don't also change the outer embed's tool.\n container.addEventListener(\"patchwork:embed-tool-changed\", (e) => {\n e.stopPropagation();\n const toolId = (e as CustomEvent<{ toolId?: string }>).detail?.toolId;\n if (toolId) this.setTool(view, container, toolId);\n });\n\n container.appendChild(patchworkView);\n return container;\n }\n\n private setTool(\n view: EditorView,\n container: HTMLElement,\n toolId: string\n ): void {\n const from = view.posAtDOM(container);\n const to = from + this.embedText.length;\n // Only rewrite if the marker is still exactly where this widget maps to.\n if (view.state.doc.sliceString(from, to) !== this.embedText) return;\n view.dispatch({\n changes: { from, to, insert: embedSyntax({ docId: this.docId, toolId }) },\n });\n }\n\n ignoreEvent() {\n // Atomic embed: the editor ignores all events; the frame handles them.\n return true;\n }\n}\n\n// Marker syntax: [patchwork:docId] or [patchwork:docId/toolId] (ids can't\n// contain `/` or `]`). We scan plain text instead of the markdown syntax tree\n// on purpose: `@codemirror/language` isn't a shared singleton across\n// patchwork's separately-bundled extensions, so `syntaxTree(state)` here would\n// read a different `Language` facet and always come back empty.\nconst EMBED_PATTERN = /\\[patchwork:([^/\\]]+)(?:\\/([^\\]]+))?\\]/g;\n\nfunction getEmbedLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n\n // Visible ranges are line-aligned and markers never span lines, so a marker\n // is always fully inside or fully outside a range.\n for (const { from, to } of view.visibleRanges) {\n const text = state.doc.sliceString(from, to);\n EMBED_PATTERN.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = EMBED_PATTERN.exec(text)) !== null) {\n const matchFrom = from + m.index;\n const matchTo = matchFrom + m[0].length;\n const [matchText, docId, toolId] = m;\n\n if (!isValidDocumentId(docId)) continue;\n\n const embed = Decoration.replace({\n widget: new EmbedWidget(docId as DocumentId, toolId ?? null, matchText),\n });\n widgets.push(embed.range(matchFrom, matchTo));\n }\n }\n\n // `true` lets CodeMirror sort the ranges defensively (matches are already in\n // document order, but this is cheap insurance).\n return Decoration.set(widgets, true);\n}\n\n// MIME types we accept document drags from. Mirrors the sideboard's convention.\nconst PATCHWORK_DND = \"text/x-patchwork-dnd\";\nconst PATCHWORK_URLS = \"text/x-patchwork-urls\";\n\ntype DocRef = { docId: DocumentId; toolId: string | null };\n\n/** Turn an automerge url or a patchwork web link into a DocumentId, or null. */\nfunction urlToDocId(raw: string): DocumentId | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Plain automerge url (optionally carrying heads/query/subpath).\n const am = trimmed.match(/automerge:([a-zA-Z0-9]+)/);\n if (am && isValidDocumentId(am[1])) return am[1] as DocumentId;\n // Patchwork web link: #doc=<documentId> (also &doc= / ?doc=).\n const web = trimmed.match(/[#&?]doc=([a-zA-Z0-9]+)/);\n if (web && isValidDocumentId(web[1])) return web[1] as DocumentId;\n return null;\n}\n\n/**\n * Read dragged documents out of a drop event, in order of format preference.\n * Only an *explicit* `toolId` on a structured item pins the tool; otherwise we\n * embed tool-less and let the view fall back to the datatype's default tool.\n */\nfunction extractDocRefs(dt: DataTransfer): DocRef[] {\n const refs: DocRef[] = [];\n const seen = new Set<string>();\n const push = (docId: DocumentId | null, toolId: string | null) => {\n if (!docId || seen.has(docId)) return;\n seen.add(docId);\n refs.push({ docId, toolId });\n };\n\n const dnd = dt.getData(PATCHWORK_DND);\n if (dnd) {\n try {\n const parsed = JSON.parse(dnd) as {\n items?: Array<{ url?: string; toolId?: string }>;\n };\n for (const item of parsed?.items ?? []) {\n if (item?.url) push(urlToDocId(item.url), item.toolId ?? null);\n }\n } catch {\n // fall through to the other formats\n }\n }\n if (refs.length > 0) return refs;\n\n const urls = dt.getData(PATCHWORK_URLS);\n if (urls) {\n try {\n const parsed: unknown = JSON.parse(urls);\n if (Array.isArray(parsed)) {\n for (const u of parsed) push(urlToDocId(String(u)), null);\n }\n } catch {\n // fall through\n }\n }\n if (refs.length > 0) return refs;\n\n const text = dt.getData(\"text/uri-list\") || dt.getData(\"text/plain\");\n if (text) {\n for (const line of text.split(/\\r?\\n/)) {\n if (line.startsWith(\"#\")) continue; // uri-list comments\n push(urlToDocId(line), null);\n }\n }\n return refs;\n}\n\nfunction embedSyntax({ docId, toolId }: DocRef): string {\n return toolId ? `[patchwork:${docId}/${toolId}]` : `[patchwork:${docId}]`;\n}\n\n/**\n * Import each OS file (dropped from the desktop or pasted) as a Patchwork\n * `file` document, returning tool-less refs to embed. Uses the realm-local\n * `window.repo` (the documented global) \u2014 fine for creating brand-new docs,\n * which aren't subject to draft remapping.\n */\nasync function importFileRefs(files: Iterable<File>): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\"[codemirror-embed] window.repo unavailable; ignoring files\");\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of files) {\n try {\n const mimeType = file.type || \"application/octet-stream\";\n const isText =\n mimeType.startsWith(\"text/\") || mimeType === \"application/json\";\n const content = isText\n ? await file.text()\n : new Uint8Array(await file.arrayBuffer());\n const parts = file.name.split(\".\");\n const extension = parts.length > 1 ? parts.pop()! : \"\";\n const handle = repo.create();\n handle.change((d: any) => {\n d[\"@patchwork\"] = { type: \"file\" };\n d.content = content;\n d.mimeType = mimeType;\n d.extension = extension;\n d.name = file.name;\n });\n const { documentId } = parseAutomergeUrl(handle.url as AutomergeUrl);\n if (isValidDocumentId(documentId)) {\n refs.push({ docId: documentId as DocumentId, toolId: null });\n }\n } catch (err) {\n console.warn(\n \"[codemirror-embed] failed to import file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\n/** Insert embeds over `from..to` (a drop point, or a pasted-over selection). */\nfunction insertRefs(\n view: EditorView,\n from: number,\n refs: DocRef[],\n to = from\n): void {\n if (refs.length === 0) return;\n // Files import asynchronously, so the doc may have shrunk in the meantime.\n const len = view.state.doc.length;\n from = Math.min(from, len);\n to = Math.min(to, len);\n let text = refs.map(embedSyntax).join(\"\\n\\n\");\n // On a non-empty line, add just enough breaks to put the embed on its own line.\n const { doc } = view.state;\n const start = doc.lineAt(from);\n const end = doc.lineAt(to);\n if (/\\S/.test(start.text) && from > start.from) text = \"\\n\" + text;\n if (/\\S/.test(end.text) && to < end.to) text = text + \"\\n\";\n view.dispatch({\n changes: { from, to, insert: text },\n selection: { anchor: from + text.length },\n });\n}\n\n/**\n * Drop handler that accepts:\n * - documents dragged from the sidebar / canvases / other tools\n * (`text/x-patchwork-dnd`, `text/x-patchwork-urls`, `text/uri-list`, and\n * `text/plain` patchwork links), and\n * - files dragged in from the operating system (imported as `file` docs).\n * Inserts `[patchwork:docId]` (or `[patchwork:docId/toolId]`) at the drop point.\n */\nfunction embedDropHandlers() {\n // Only claim the *dragover* for unambiguous patchwork/OS drags \u2014 plain\n // text/uri-list can be an ordinary in-editor text drag, which we must not\n // swallow. (`Files` covers OS drags, whose `dt.files` is empty until drop.)\n const wantsDragover = (dt: DataTransfer | null): boolean =>\n !!dt &&\n (dt.types.includes(\"Files\") ||\n dt.types.includes(PATCHWORK_DND) ||\n dt.types.includes(PATCHWORK_URLS));\n\n return EditorView.domEventHandlers({\n dragover(event) {\n const dt = event.dataTransfer;\n if (!wantsDragover(dt)) return false;\n event.preventDefault();\n // Doc drags insert a *reference* to the same automerge doc, so show the\n // link cursor; OS file drags genuinely copy content into new docs.\n if (dt) dt.dropEffect = dt.types.includes(\"Files\") ? \"copy\" : \"link\";\n return true;\n },\n drop(event, view) {\n const dt = event.dataTransfer;\n if (!dt) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n\n // OS files import asynchronously; insert once the docs exist.\n if (dt.files && dt.files.length > 0) {\n event.preventDefault();\n void importFileRefs(Array.from(dt.files)).then((refs) =>\n insertRefs(view, pos, refs)\n );\n return true;\n }\n\n // Otherwise only handle the drop if it actually resolves to patchwork\n // docs (dnd/urls always do; uri-list/plain only for patchwork links).\n // If not, let CodeMirror handle it as a normal text drop.\n const refs = extractDocRefs(dt);\n if (refs.length === 0) return false;\n event.preventDefault();\n insertRefs(view, pos, refs);\n return true;\n },\n paste(event, view) {\n // Image pastes (screenshots, \"Copy Image\") become embedded `file` docs.\n // A paste that also carries text is left to CodeMirror: rich text from\n // office apps attaches a picture of itself, and a Finder copy carries\n // the file's icon alongside its name.\n const dt = event.clipboardData;\n if (!dt || dt.getData(\"text/plain\")) return false;\n const images = Array.from(dt.files ?? []).filter((f) =>\n f.type.startsWith(\"image/\")\n );\n if (images.length === 0) return false;\n event.preventDefault();\n const { from, to } = view.state.selection.main;\n void importFileRefs(images).then((refs) =>\n insertRefs(view, from, refs, to)\n );\n return true;\n },\n });\n}\n\nconst embedPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getEmbedLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Viewport too, so newly-visible markers get decorated.\n if (update.docChanged || update.viewportChanged) {\n this.decorations = getEmbedLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n // Atomic: the cursor skips embeds and delete removes the whole marker.\n provide: (plugin) =>\n EditorView.atomicRanges.of(\n (view) => view.plugin(plugin)?.decorations ?? Decoration.none\n ),\n }\n);\n\nexport function markdownEmbed() {\n // `dropCursor` observes events, so it coexists with our dragover handler.\n return [embedPlugin, embedTheme, embedDropHandlers(), dropCursor()];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\n// Outer container only; all embed chrome lives in the shared \"embed\" tool.\nexport const embedTheme = EditorView.baseTheme({\n \".cm-embed\": {\n display: \"block\",\n border: \"var(--studio-border-width, 1px) var(--studio-border-style, solid)\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n },\n \"&light .cm-embed\": {\n borderColor: \"#ddd\",\n },\n \"&dark .cm-embed\": {\n borderColor: \"#333\",\n },\n \".cm-embed > patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n dropCursor,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport { Range } from \"@codemirror/state\";\nimport {\n type AutomergeUrl,\n type DocumentId,\n isValidDocumentId,\n parseAutomergeUrl,\n} from \"@automerge/automerge-repo/slim\";\nimport { embedTheme } from \"../themes/embed.ts\";\n\n/**\n * Renders an embedded doc through the shared \"embed\" tool, which draws the\n * chrome and nests the content view. The inner tool travels via the\n * `embed-tool-id` attribute; tool picks come back as\n * `patchwork:embed-tool-changed` events and are persisted by rewriting the\n * marker (which recreates the widget).\n */\nclass EmbedWidget extends WidgetType {\n readonly docId: DocumentId;\n // `null` = no pinned tool; the embed frame uses the datatype's fallback.\n readonly toolId: string | null;\n readonly embedText: string;\n\n constructor(docId: DocumentId, toolId: string | null, embedText: string) {\n super();\n this.docId = docId;\n this.toolId = toolId;\n this.embedText = embedText;\n }\n\n eq(other: EmbedWidget) {\n return other.docId === this.docId && other.toolId === this.toolId;\n }\n\n toDOM(view: EditorView) {\n const container = document.createElement(\"div\");\n container.className = \"cm-embed\";\n\n // An image with no pinned tool is just drawn, with no frame round it. We\n // can't tell until the doc loads, so hold off mounting the frame until then.\n if (this.toolId) {\n this.mountFrame(view, container);\n } else {\n const cleanup = mountImageOrFrame(this.docId, container, () =>\n this.mountFrame(view, container)\n );\n cleanups.set(container, cleanup);\n }\n return container;\n }\n\n destroy(dom: HTMLElement) {\n cleanups.get(dom)?.();\n cleanups.delete(dom);\n }\n\n private mountFrame(view: EditorView, container: HTMLElement): void {\n const patchworkView = document.createElement(\"patchwork-view\");\n // No heads in the url: the embed follows whatever version the host\n // resolves \u2014 the active checkpoint when one is checked out, live otherwise.\n patchworkView.setAttribute(\"doc-url\", `automerge:${this.docId}`);\n patchworkView.setAttribute(\"tool-id\", \"embed\");\n if (this.toolId) patchworkView.setAttribute(\"embed-tool-id\", this.toolId);\n // Explicit inline height: without it the element collapses to 0px (the\n // stylesheet rule isn't reliably applied here).\n patchworkView.style.display = \"block\";\n patchworkView.style.height = \"500px\";\n patchworkView.style.width = \"100%\";\n\n // Persist tool picks into the marker text. Stop propagation so nested\n // embeds don't also change the outer embed's tool.\n container.addEventListener(\"patchwork:embed-tool-changed\", (e) => {\n e.stopPropagation();\n const toolId = (e as CustomEvent<{ toolId?: string }>).detail?.toolId;\n if (toolId) this.setTool(view, container, toolId);\n });\n\n container.appendChild(patchworkView);\n }\n\n private setTool(\n view: EditorView,\n container: HTMLElement,\n toolId: string\n ): void {\n const from = view.posAtDOM(container);\n const to = from + this.embedText.length;\n // Only rewrite if the marker is still exactly where this widget maps to.\n if (view.state.doc.sliceString(from, to) !== this.embedText) return;\n view.dispatch({\n changes: { from, to, insert: embedSyntax({ docId: this.docId, toolId }) },\n });\n }\n\n ignoreEvent() {\n // Atomic embed: the editor ignores all events; the frame handles them.\n return true;\n }\n}\n\nconst cleanups = new WeakMap<HTMLElement, () => void>();\n\nconst IMAGE_EXTENSIONS = [\"svg\", \"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"bmp\"];\n\ntype FileDoc = {\n \"@patchwork\"?: { type?: string };\n content?: unknown;\n mimeType?: string;\n extension?: string;\n name?: string;\n};\n\nfunction isImageDoc(doc: FileDoc | undefined): boolean {\n if (doc?.[\"@patchwork\"]?.type !== \"file\") return false;\n if (!(doc.content instanceof Uint8Array)) return false;\n return (\n !!doc.mimeType?.startsWith(\"image/\") ||\n IMAGE_EXTENSIONS.includes(doc.extension?.toLowerCase() ?? \"\")\n );\n}\n\n/**\n * Look the doc up; if it's an image file, draw it as a bare `<img>` that\n * follows the doc's content. Anything else (or no `window.repo`) gets the\n * normal embed frame. Returns a cleanup for when the widget goes away.\n *\n * Reads through the realm-local `window.repo`, so an image changed only in a\n * draft shows its main-branch bytes here.\n */\nfunction mountImageOrFrame(\n docId: DocumentId,\n container: HTMLElement,\n mountFrame: () => void\n): () => void {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo?.find) {\n mountFrame();\n return () => {};\n }\n\n let disposed = false;\n let objectUrl: string | undefined;\n let handle: any;\n let img: HTMLImageElement | undefined;\n\n const render = () => {\n const doc = handle.doc() as FileDoc | undefined;\n if (!isImageDoc(doc)) return;\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n objectUrl = URL.createObjectURL(\n new Blob([doc!.content as BlobPart], { type: doc!.mimeType || \"\" })\n );\n img!.src = objectUrl;\n img!.alt = doc!.name ?? \"\";\n };\n\n Promise.resolve(repo.find(`automerge:${docId}`)).then(\n (h) => {\n if (disposed) return;\n handle = h;\n if (!isImageDoc(handle.doc())) {\n mountFrame();\n return;\n }\n container.classList.add(\"cm-embed-image\");\n img = document.createElement(\"img\");\n container.appendChild(img);\n render();\n handle.on(\"change\", render);\n },\n () => {\n if (!disposed) mountFrame();\n }\n );\n\n return () => {\n disposed = true;\n handle?.off?.(\"change\", render);\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n}\n\n// Marker syntax: [patchwork:docId] or [patchwork:docId/toolId] (ids can't\n// contain `/` or `]`). We scan plain text instead of the markdown syntax tree\n// on purpose: `@codemirror/language` isn't a shared singleton across\n// patchwork's separately-bundled extensions, so `syntaxTree(state)` here would\n// read a different `Language` facet and always come back empty.\nconst EMBED_PATTERN = /\\[patchwork:([^/\\]]+)(?:\\/([^\\]]+))?\\]/g;\n\nfunction getEmbedLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n\n // Visible ranges are line-aligned and markers never span lines, so a marker\n // is always fully inside or fully outside a range.\n for (const { from, to } of view.visibleRanges) {\n const text = state.doc.sliceString(from, to);\n EMBED_PATTERN.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = EMBED_PATTERN.exec(text)) !== null) {\n const matchFrom = from + m.index;\n const matchTo = matchFrom + m[0].length;\n const [matchText, docId, toolId] = m;\n\n if (!isValidDocumentId(docId)) continue;\n\n const embed = Decoration.replace({\n widget: new EmbedWidget(docId as DocumentId, toolId ?? null, matchText),\n });\n widgets.push(embed.range(matchFrom, matchTo));\n }\n }\n\n // `true` lets CodeMirror sort the ranges defensively (matches are already in\n // document order, but this is cheap insurance).\n return Decoration.set(widgets, true);\n}\n\n// MIME types we accept document drags from. Mirrors the sideboard's convention.\nconst PATCHWORK_DND = \"text/x-patchwork-dnd\";\nconst PATCHWORK_URLS = \"text/x-patchwork-urls\";\n\ntype DocRef = { docId: DocumentId; toolId: string | null };\n\n/** Turn an automerge url or a patchwork web link into a DocumentId, or null. */\nfunction urlToDocId(raw: string): DocumentId | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Plain automerge url (optionally carrying heads/query/subpath).\n const am = trimmed.match(/automerge:([a-zA-Z0-9]+)/);\n if (am && isValidDocumentId(am[1])) return am[1] as DocumentId;\n // Patchwork web link: #doc=<documentId> (also &doc= / ?doc=).\n const web = trimmed.match(/[#&?]doc=([a-zA-Z0-9]+)/);\n if (web && isValidDocumentId(web[1])) return web[1] as DocumentId;\n return null;\n}\n\n/**\n * Read dragged documents out of a drop event, in order of format preference.\n * Only an *explicit* `toolId` on a structured item pins the tool; otherwise we\n * embed tool-less and let the view fall back to the datatype's default tool.\n */\nfunction extractDocRefs(dt: DataTransfer): DocRef[] {\n const refs: DocRef[] = [];\n const seen = new Set<string>();\n const push = (docId: DocumentId | null, toolId: string | null) => {\n if (!docId || seen.has(docId)) return;\n seen.add(docId);\n refs.push({ docId, toolId });\n };\n\n const dnd = dt.getData(PATCHWORK_DND);\n if (dnd) {\n try {\n const parsed = JSON.parse(dnd) as {\n items?: Array<{ url?: string; toolId?: string }>;\n };\n for (const item of parsed?.items ?? []) {\n if (item?.url) push(urlToDocId(item.url), item.toolId ?? null);\n }\n } catch {\n // fall through to the other formats\n }\n }\n if (refs.length > 0) return refs;\n\n const urls = dt.getData(PATCHWORK_URLS);\n if (urls) {\n try {\n const parsed: unknown = JSON.parse(urls);\n if (Array.isArray(parsed)) {\n for (const u of parsed) push(urlToDocId(String(u)), null);\n }\n } catch {\n // fall through\n }\n }\n if (refs.length > 0) return refs;\n\n const text = dt.getData(\"text/uri-list\") || dt.getData(\"text/plain\");\n if (text) {\n for (const line of text.split(/\\r?\\n/)) {\n if (line.startsWith(\"#\")) continue; // uri-list comments\n push(urlToDocId(line), null);\n }\n }\n return refs;\n}\n\nfunction embedSyntax({ docId, toolId }: DocRef): string {\n return toolId ? `[patchwork:${docId}/${toolId}]` : `[patchwork:${docId}]`;\n}\n\n/**\n * Import each OS file (dropped from the desktop or pasted) as a Patchwork\n * `file` document, returning tool-less refs to embed. Uses the realm-local\n * `window.repo` (the documented global) \u2014 fine for creating brand-new docs,\n * which aren't subject to draft remapping.\n */\nasync function importFileRefs(files: Iterable<File>): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\"[codemirror-embed] window.repo unavailable; ignoring files\");\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of files) {\n try {\n const mimeType = file.type || \"application/octet-stream\";\n const isText =\n mimeType.startsWith(\"text/\") || mimeType === \"application/json\";\n const content = isText\n ? await file.text()\n : new Uint8Array(await file.arrayBuffer());\n const parts = file.name.split(\".\");\n const extension = parts.length > 1 ? parts.pop()! : \"\";\n const handle = repo.create();\n handle.change((d: any) => {\n d[\"@patchwork\"] = { type: \"file\" };\n d.content = content;\n d.mimeType = mimeType;\n d.extension = extension;\n d.name = file.name;\n });\n const { documentId } = parseAutomergeUrl(handle.url as AutomergeUrl);\n if (isValidDocumentId(documentId)) {\n refs.push({ docId: documentId as DocumentId, toolId: null });\n }\n } catch (err) {\n console.warn(\n \"[codemirror-embed] failed to import file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\n/** Insert embeds over `from..to` (a drop point, or a pasted-over selection). */\nfunction insertRefs(\n view: EditorView,\n from: number,\n refs: DocRef[],\n to = from\n): void {\n if (refs.length === 0) return;\n // Files import asynchronously, so the doc may have shrunk in the meantime.\n const len = view.state.doc.length;\n from = Math.min(from, len);\n to = Math.min(to, len);\n let text = refs.map(embedSyntax).join(\"\\n\\n\");\n // On a non-empty line, add just enough breaks to put the embed on its own line.\n const { doc } = view.state;\n const start = doc.lineAt(from);\n const end = doc.lineAt(to);\n if (/\\S/.test(start.text) && from > start.from) text = \"\\n\" + text;\n if (/\\S/.test(end.text) && to < end.to) text = text + \"\\n\";\n view.dispatch({\n changes: { from, to, insert: text },\n selection: { anchor: from + text.length },\n });\n}\n\n/**\n * Drop handler that accepts:\n * - documents dragged from the sidebar / canvases / other tools\n * (`text/x-patchwork-dnd`, `text/x-patchwork-urls`, `text/uri-list`, and\n * `text/plain` patchwork links), and\n * - files dragged in from the operating system (imported as `file` docs).\n * Inserts `[patchwork:docId]` (or `[patchwork:docId/toolId]`) at the drop point.\n */\nfunction embedDropHandlers() {\n // Only claim the *dragover* for unambiguous patchwork/OS drags \u2014 plain\n // text/uri-list can be an ordinary in-editor text drag, which we must not\n // swallow. (`Files` covers OS drags, whose `dt.files` is empty until drop.)\n const wantsDragover = (dt: DataTransfer | null): boolean =>\n !!dt &&\n (dt.types.includes(\"Files\") ||\n dt.types.includes(PATCHWORK_DND) ||\n dt.types.includes(PATCHWORK_URLS));\n\n return EditorView.domEventHandlers({\n dragover(event) {\n const dt = event.dataTransfer;\n if (!wantsDragover(dt)) return false;\n event.preventDefault();\n // Doc drags insert a *reference* to the same automerge doc, so show the\n // link cursor; OS file drags genuinely copy content into new docs.\n if (dt) dt.dropEffect = dt.types.includes(\"Files\") ? \"copy\" : \"link\";\n return true;\n },\n drop(event, view) {\n const dt = event.dataTransfer;\n if (!dt) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n\n // OS files import asynchronously; insert once the docs exist.\n if (dt.files && dt.files.length > 0) {\n event.preventDefault();\n void importFileRefs(Array.from(dt.files)).then((refs) =>\n insertRefs(view, pos, refs)\n );\n return true;\n }\n\n // Otherwise only handle the drop if it actually resolves to patchwork\n // docs (dnd/urls always do; uri-list/plain only for patchwork links).\n // If not, let CodeMirror handle it as a normal text drop.\n const refs = extractDocRefs(dt);\n if (refs.length === 0) return false;\n event.preventDefault();\n insertRefs(view, pos, refs);\n return true;\n },\n paste(event, view) {\n // Image pastes (screenshots, \"Copy Image\") become embedded `file` docs.\n // A paste that also carries text is left to CodeMirror: rich text from\n // office apps attaches a picture of itself, and a Finder copy carries\n // the file's icon alongside its name.\n const dt = event.clipboardData;\n if (!dt || dt.getData(\"text/plain\")) return false;\n const images = Array.from(dt.files ?? []).filter((f) =>\n f.type.startsWith(\"image/\")\n );\n if (images.length === 0) return false;\n event.preventDefault();\n const { from, to } = view.state.selection.main;\n void importFileRefs(images).then((refs) =>\n insertRefs(view, from, refs, to)\n );\n return true;\n },\n });\n}\n\nconst embedPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getEmbedLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Viewport too, so newly-visible markers get decorated.\n if (update.docChanged || update.viewportChanged) {\n this.decorations = getEmbedLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n // Atomic: the cursor skips embeds and delete removes the whole marker.\n provide: (plugin) =>\n EditorView.atomicRanges.of(\n (view) => view.plugin(plugin)?.decorations ?? Decoration.none\n ),\n }\n);\n\nexport function markdownEmbed() {\n // `dropCursor` observes events, so it coexists with our dragover handler.\n return [embedPlugin, embedTheme, embedDropHandlers(), dropCursor()];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\n// Outer container only; all embed chrome lives in the shared \"embed\" tool.\nexport const embedTheme = EditorView.baseTheme({\n \".cm-embed\": {\n display: \"block\",\n border: \"var(--studio-border-width, 1px) var(--studio-border-style, solid)\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n },\n \"&light .cm-embed\": {\n borderColor: \"#ddd\",\n },\n \"&dark .cm-embed\": {\n borderColor: \"#333\",\n },\n // Images are just images: no border, natural size, never wider than the text.\n \"&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image\": {\n border: \"none\",\n borderRadius: \"0\",\n },\n \".cm-embed-image > img\": {\n display: \"block\",\n maxWidth: \"100%\",\n height: \"auto\",\n },\n \".cm-embed > patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n});\n"],
5
+ "mappings": "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",
6
6
  "names": ["EditorView", "EditorView", "refs"]
7
7
  }
@@ -1 +1 @@
1
- automerge:y8E8rcijiLVkGRwVTPxHBHm4jCU
1
+ automerge:2QodnpbX8YZpJikr1emmvtfpXYwR