@inkandswitch/patchwork-pkg-base 0.0.23 → 0.0.25

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/modules.json CHANGED
@@ -15,6 +15,7 @@
15
15
  "./packages/doc-openwith/",
16
16
  "./packages/doc-presence/",
17
17
  "./packages/drafts/",
18
+ "./packages/embed/",
18
19
  "./packages/file/",
19
20
  "./packages/folder/",
20
21
  "./packages/history-view/",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -18,5 +18,8 @@
18
18
  "watch": "node scripts/watch.mjs",
19
19
  "deploy:tools": "pnpm build:static && pnpm dlx netlify-cli deploy --dir=static-dist --prod"
20
20
  },
21
- "packageManager": "pnpm@11.15.1"
21
+ "packageManager": "pnpm@11.15.1",
22
+ "patchwork": {
23
+ "static": "static-dist"
24
+ }
22
25
  }
@@ -3,7 +3,8 @@ import {
3
3
  EditorView as EditorView2,
4
4
  Decoration,
5
5
  WidgetType,
6
- ViewPlugin
6
+ ViewPlugin,
7
+ dropCursor
7
8
  } from "@codemirror/view";
8
9
  import {
9
10
  isValidDocumentId,
@@ -25,74 +26,17 @@ var embedTheme = EditorView.baseTheme({
25
26
  "&dark .cm-embed": {
26
27
  borderColor: "#333"
27
28
  },
28
- ".cm-embed-label": {
29
- fontFamily: "monospace",
30
- padding: "4px 8px",
31
- borderBottom: "1px solid",
32
- whiteSpace: "nowrap",
33
- overflow: "hidden",
34
- textOverflow: "ellipsis",
35
- cursor: "text",
36
- display: "flex",
37
- alignItems: "center",
38
- gap: "8px"
39
- },
40
- ".cm-embed-label-text": {
41
- flex: "1",
42
- minWidth: "0",
43
- overflow: "hidden",
44
- textOverflow: "ellipsis",
45
- opacity: 0.7,
46
- "&:hover": {
47
- opacity: 1
48
- }
49
- },
50
- ".cm-embed-open-link": {
51
- display: "inline-flex",
52
- alignItems: "center",
53
- justifyContent: "center",
54
- width: "16px",
55
- height: "16px",
56
- cursor: "pointer",
57
- flexShrink: "0",
58
- border: "none",
59
- background: "none",
60
- padding: "0",
61
- color: "inherit",
62
- opacity: 0.7,
63
- "&:hover": {
64
- opacity: 1
65
- }
66
- },
67
- "&light .cm-embed-label": {
68
- borderBottomColor: "#ddd"
69
- },
70
- "&dark .cm-embed-label": {
71
- borderBottomColor: "#333"
72
- },
73
- ".cm-embed patchwork-view": {
29
+ ".cm-embed > patchwork-view": {
74
30
  display: "block",
75
31
  height: "500px",
76
32
  width: "100%"
77
- },
78
- ".cm-embed patchwork-view > *": {
79
- height: "100%",
80
- width: "100%"
81
33
  }
82
34
  });
83
35
 
84
- // src/extensions/icons.ts
85
- var openLinkIcon = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
86
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
87
- <polyline points="15 3 21 3 21 9"></polyline>
88
- <line x1="10" y1="14" x2="21" y2="3"></line>
89
- </svg>`;
90
-
91
36
  // src/extensions/embed.ts
92
37
  var EmbedWidget = class extends WidgetType {
93
38
  docId;
94
- // `null` means "no explicit tool": <patchwork-view> falls back to the
95
- // default tool registered for the document's datatype.
39
+ // `null` = no pinned tool; the embed frame uses the datatype's fallback.
96
40
  toolId;
97
41
  embedText;
98
42
  constructor(docId, toolId, embedText) {
@@ -104,48 +48,33 @@ var EmbedWidget = class extends WidgetType {
104
48
  eq(other) {
105
49
  return other.docId === this.docId && other.toolId === this.toolId;
106
50
  }
107
- toDOM() {
51
+ toDOM(view) {
108
52
  const container = document.createElement("div");
109
53
  container.className = "cm-embed";
110
- const label = document.createElement("div");
111
- label.className = "cm-embed-label";
112
- const labelText = document.createElement("span");
113
- labelText.className = "cm-embed-label-text";
114
- labelText.textContent = this.embedText;
115
- labelText.title = "Click to edit";
116
- const openLink = document.createElement("button");
117
- openLink.className = "cm-embed-open-link";
118
- openLink.title = "Open document";
119
- openLink.innerHTML = openLinkIcon;
120
- openLink.onclick = (e) => {
121
- e.preventDefault();
54
+ const patchworkView = document.createElement("patchwork-view");
55
+ patchworkView.setAttribute("doc-url", `automerge:${this.docId}`);
56
+ patchworkView.setAttribute("tool-id", "embed");
57
+ if (this.toolId) patchworkView.setAttribute("embed-tool-id", this.toolId);
58
+ patchworkView.style.display = "block";
59
+ patchworkView.style.height = "500px";
60
+ patchworkView.style.width = "100%";
61
+ container.addEventListener("patchwork:embed-tool-changed", (e) => {
122
62
  e.stopPropagation();
123
- const params = new URLSearchParams();
124
- params.set("doc", this.docId);
125
- if (this.toolId) params.set("tool", this.toolId);
126
- window.location.hash = params.toString();
127
- };
128
- label.appendChild(labelText);
129
- label.appendChild(openLink);
130
- const view = document.createElement("patchwork-view");
131
- view.setAttribute("doc-url", `automerge:${this.docId}`);
132
- if (this.toolId) view.setAttribute("tool-id", this.toolId);
133
- view.style.display = "block";
134
- view.style.height = "500px";
135
- view.style.width = "100%";
136
- container.appendChild(label);
137
- container.appendChild(view);
63
+ const toolId = e.detail?.toolId;
64
+ if (toolId) this.setTool(view, container, toolId);
65
+ });
66
+ container.appendChild(patchworkView);
138
67
  return container;
139
68
  }
140
- ignoreEvent(e) {
141
- if (e.type === "mousedown" && e.target instanceof Element) {
142
- if (e.target.classList.contains("cm-embed-label-text")) {
143
- return false;
144
- }
145
- if (e.target.classList.contains("cm-embed-open-link") || e.target.closest(".cm-embed-open-link")) {
146
- return true;
147
- }
148
- }
69
+ setTool(view, container, toolId) {
70
+ const from = view.posAtDOM(container);
71
+ const to = from + this.embedText.length;
72
+ if (view.state.doc.sliceString(from, to) !== this.embedText) return;
73
+ view.dispatch({
74
+ changes: { from, to, insert: embedSyntax({ docId: this.docId, toolId }) }
75
+ });
76
+ }
77
+ ignoreEvent() {
149
78
  return true;
150
79
  }
151
80
  };
@@ -153,7 +82,6 @@ var EMBED_PATTERN = /\[patchwork:([^/\]]+)(?:\/([^\]]+))?\]/g;
153
82
  function getEmbedLinks(view) {
154
83
  const widgets = [];
155
84
  const { state } = view;
156
- const selection = state.selection.main;
157
85
  for (const { from, to } of view.visibleRanges) {
158
86
  const text = state.doc.sliceString(from, to);
159
87
  EMBED_PATTERN.lastIndex = 0;
@@ -162,9 +90,6 @@ function getEmbedLinks(view) {
162
90
  const matchFrom = from + m.index;
163
91
  const matchTo = matchFrom + m[0].length;
164
92
  const [matchText, docId, toolId] = m;
165
- const cursorInLink = selection.from >= matchFrom && selection.from <= matchTo;
166
- const selectionSpansLink = selection.from < matchFrom && selection.to > matchTo;
167
- if (cursorInLink || selectionSpansLink) continue;
168
93
  if (!isValidDocumentId(docId)) continue;
169
94
  const embed = Decoration.replace({
170
95
  widget: new EmbedWidget(docId, toolId ?? null, matchText)
@@ -267,7 +192,12 @@ async function fileDropRefs(files) {
267
192
  }
268
193
  function insertRefs(view, pos, refs) {
269
194
  if (refs.length === 0) return;
270
- const text = refs.map(embedSyntax).join("\n\n");
195
+ let text = refs.map(embedSyntax).join("\n\n");
196
+ const line = view.state.doc.lineAt(pos);
197
+ if (/\S/.test(line.text)) {
198
+ if (pos > line.from) text = "\n" + text;
199
+ if (pos < line.to) text = text + "\n";
200
+ }
271
201
  view.dispatch({
272
202
  changes: { from: pos, insert: text },
273
203
  selection: { anchor: pos + text.length }
@@ -277,9 +207,10 @@ function embedDropHandlers() {
277
207
  const wantsDragover = (dt) => !!dt && (dt.types.includes("Files") || dt.types.includes(PATCHWORK_DND) || dt.types.includes(PATCHWORK_URLS));
278
208
  return EditorView2.domEventHandlers({
279
209
  dragover(event) {
280
- if (!wantsDragover(event.dataTransfer)) return false;
210
+ const dt = event.dataTransfer;
211
+ if (!wantsDragover(dt)) return false;
281
212
  event.preventDefault();
282
- if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
213
+ if (dt) dt.dropEffect = dt.types.includes("Files") ? "copy" : "link";
283
214
  return true;
284
215
  },
285
216
  drop(event, view) {
@@ -307,17 +238,21 @@ var embedPlugin = ViewPlugin.fromClass(
307
238
  this.decorations = getEmbedLinks(view);
308
239
  }
309
240
  update(update) {
310
- if (update.docChanged || update.selectionSet || update.viewportChanged) {
241
+ if (update.docChanged || update.viewportChanged) {
311
242
  this.decorations = getEmbedLinks(update.view);
312
243
  }
313
244
  }
314
245
  },
315
246
  {
316
- decorations: (v) => v.decorations
247
+ decorations: (v) => v.decorations,
248
+ // Atomic: the cursor skips embeds and delete removes the whole marker.
249
+ provide: (plugin) => EditorView2.atomicRanges.of(
250
+ (view) => view.plugin(plugin)?.decorations ?? Decoration.none
251
+ )
317
252
  }
318
253
  );
319
254
  function markdownEmbed() {
320
- return [embedPlugin, embedTheme, embedDropHandlers()];
255
+ return [embedPlugin, embedTheme, embedDropHandlers(), dropCursor()];
321
256
  }
322
257
  export {
323
258
  markdownEmbed
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/extensions/embed.ts", "../../src/themes/embed.ts", "../../src/extensions/icons.ts"],
4
- "sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\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\";\nimport { openLinkIcon } from \"./icons.ts\";\n\n/**\n * Widget to render an embedded <patchwork-view> element in a CodeMirror editor.\n */\nclass EmbedWidget extends WidgetType {\n readonly docId: DocumentId;\n // `null` means \"no explicit tool\": <patchwork-view> falls back to the\n // default tool registered for the document's datatype.\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() {\n const container = document.createElement(\"div\");\n container.className = \"cm-embed\";\n\n const label = document.createElement(\"div\");\n label.className = \"cm-embed-label\";\n\n const labelText = document.createElement(\"span\");\n labelText.className = \"cm-embed-label-text\";\n labelText.textContent = this.embedText;\n labelText.title = \"Click to edit\";\n\n const openLink = document.createElement(\"button\");\n openLink.className = \"cm-embed-open-link\";\n openLink.title = \"Open document\";\n openLink.innerHTML = openLinkIcon;\n\n openLink.onclick = (e) => {\n e.preventDefault();\n e.stopPropagation();\n const params = new URLSearchParams();\n params.set(\"doc\", this.docId);\n // Tool-less embeds open with the datatype's default tool.\n if (this.toolId) params.set(\"tool\", this.toolId);\n window.location.hash = params.toString();\n };\n\n label.appendChild(labelText);\n label.appendChild(openLink);\n\n const view = document.createElement(\"patchwork-view\");\n // Name the doc without heads. Resolution (OverlayRepo + the drafts\n // `repo:handle-descriptor` answer) pins it to the active checkpoint when one\n // is checked out, so the embed freezes with the document it lives in;\n // otherwise it renders live.\n view.setAttribute(\"doc-url\", `automerge:${this.docId}`);\n if (this.toolId) view.setAttribute(\"tool-id\", this.toolId);\n // The <patchwork-view> needs an explicit, non-zero height set inline:\n // without it the element collapses to 0px and the embedded tool never\n // renders. (The stylesheet rule isn't reliably applied here, so we set it\n // directly on the element.)\n view.style.display = \"block\";\n view.style.height = \"500px\";\n view.style.width = \"100%\";\n\n container.appendChild(label);\n container.appendChild(view);\n\n return container;\n }\n\n ignoreEvent(e: Event) {\n if (e.type === \"mousedown\" && e.target instanceof Element) {\n // Allow clicks on the label text to pass through for editing\n if (e.target.classList.contains(\"cm-embed-label-text\")) {\n return false; // Let the editor handle it\n }\n // Block clicks on the open link button (let button handle it)\n if (\n e.target.classList.contains(\"cm-embed-open-link\") ||\n e.target.closest(\".cm-embed-open-link\")\n ) {\n return true; // Block from editor\n }\n }\n // Block other events from reaching the editor (let patchwork-view handle them)\n return true;\n }\n}\n\n// Embed marker syntax: [patchwork:docId] or [patchwork:docId/toolId]. The tool\n// id is optional; when absent the embed falls back to the datatype's default\n// tool. The doc id / tool id cannot contain `/` or `]`.\n//\n// We scan the document text directly rather than walking the markdown syntax\n// tree on purpose: `@codemirror/language` is not a shared singleton across\n// patchwork's separately-bundled CodeMirror extensions, so `syntaxTree(state)`\n// here reads a different `Language` facet than the markdown tool populates and\n// always comes back empty. Plain-text scanning keeps this extension\n// self-contained and free of any `@codemirror/language` dependency.\nconst EMBED_PATTERN = /\\[patchwork:([^/\\]]+)(?:\\/([^\\]]+))?\\]/g;\n\nfunction getEmbedLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n const selection = state.selection.main;\n\n // Scan only the visible ranges. Markers never span a line break, and\n // CodeMirror's visible ranges are line-aligned, so a marker is either fully\n // inside a range or fully outside it.\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 // Show raw text (no widget) while the cursor is on the marker or a\n // selection spans it, so it can be edited.\n const cursorInLink =\n selection.from >= matchFrom && selection.from <= matchTo;\n const selectionSpansLink =\n selection.from < matchFrom && selection.to > matchTo;\n if (cursorInLink || selectionSpansLink) continue;\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\n// (duplicated on purpose \u2014 see the DnD notes; a shared package is a later step).\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 * (The previous code mis-read `item.type` \u2014 a datatype \u2014 as a tool id, so any\n * source whose datatype != tool id produced a broken embed, and sources that\n * set only urls were dropped entirely.)\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 as a Patchwork `file` document,\n * returning tool-less refs to embed. Uses the realm-local `window.repo` (the\n * documented global) \u2014 fine for creating brand-new docs, which aren't subject\n * to draft remapping.\n */\nasync function fileDropRefs(files: FileList): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\n \"[codemirror-embed] window.repo unavailable; ignoring dropped files\"\n );\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of Array.from(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 dropped file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\nfunction insertRefs(view: EditorView, pos: number, refs: DocRef[]): void {\n if (refs.length === 0) return;\n const text = refs.map(embedSyntax).join(\"\\n\\n\");\n view.dispatch({\n changes: { from: pos, insert: text },\n selection: { anchor: pos + 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 if (!wantsDragover(event.dataTransfer)) return false;\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = \"copy\";\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 fileDropRefs(dt.files).then((refs) => insertRefs(view, pos, refs));\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 });\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 // Recompute when the document changes, the selection moves, or the\n // viewport scrolls (so newly-visible markers get decorated).\n if (update.docChanged || update.selectionSet || update.viewportChanged) {\n this.decorations = getEmbedLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n }\n);\n\nexport function markdownEmbed() {\n return [embedPlugin, embedTheme, embedDropHandlers()];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\nexport const embedTheme = EditorView.baseTheme({\n \".cm-embed\": {\n display: \"block\",\n border: \"1px 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-label\": {\n fontFamily: \"monospace\",\n padding: \"4px 8px\",\n borderBottom: \"1px solid\",\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n cursor: \"text\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n },\n \".cm-embed-label-text\": {\n flex: \"1\",\n minWidth: \"0\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n opacity: 0.7,\n \"&:hover\": {\n opacity: 1,\n },\n },\n \".cm-embed-open-link\": {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"16px\",\n height: \"16px\",\n cursor: \"pointer\",\n flexShrink: \"0\",\n border: \"none\",\n background: \"none\",\n padding: \"0\",\n color: \"inherit\",\n opacity: 0.7,\n \"&:hover\": {\n opacity: 1,\n },\n },\n \"&light .cm-embed-label\": {\n borderBottomColor: \"#ddd\",\n },\n \"&dark .cm-embed-label\": {\n borderBottomColor: \"#333\",\n },\n \".cm-embed patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n \".cm-embed patchwork-view > *\": {\n height: \"100%\",\n width: \"100%\",\n },\n});\n", "export const openLinkIcon = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>\n <polyline points=\"15 3 21 3 21 9\"></polyline>\n <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"></line>\n</svg>`;\n"],
5
- "mappings": "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",
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\n// (duplicated on purpose \u2014 see the DnD notes; a shared package is a later step).\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 * (The previous code mis-read `item.type` \u2014 a datatype \u2014 as a tool id, so any\n * source whose datatype != tool id produced a broken embed, and sources that\n * set only urls were dropped entirely.)\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 as a Patchwork `file` document,\n * returning tool-less refs to embed. Uses the realm-local `window.repo` (the\n * documented global) \u2014 fine for creating brand-new docs, which aren't subject\n * to draft remapping.\n */\nasync function fileDropRefs(files: FileList): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\n \"[codemirror-embed] window.repo unavailable; ignoring dropped files\"\n );\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of Array.from(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 dropped file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\nfunction insertRefs(view: EditorView, pos: number, refs: DocRef[]): void {\n if (refs.length === 0) return;\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 line = view.state.doc.lineAt(pos);\n if (/\\S/.test(line.text)) {\n if (pos > line.from) text = \"\\n\" + text;\n if (pos < line.to) text = text + \"\\n\";\n }\n view.dispatch({\n changes: { from: pos, insert: text },\n selection: { anchor: pos + 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 fileDropRefs(dt.files).then((refs) => insertRefs(view, pos, refs));\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 });\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: \"1px 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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",
6
6
  "names": ["EditorView", "EditorView", "refs"]
7
7
  }
@@ -0,0 +1,236 @@
1
+ (function() {
2
+ "use strict";
3
+ try {
4
+ if (typeof document != "undefined") {
5
+ var elementStyle = document.createElement("style");
6
+ elementStyle.appendChild(document.createTextNode("@layer package {\n :root,\n :host,\n [theme] {\n --embed-frame-bg: var(--editor-fill, white);\n --embed-frame-fg: var(--editor-line, black);\n --embed-frame-muted: var(--editor-line-offset-30, #666);\n --embed-frame-border: var(--editor-fill-offset-20, #ddd);\n --embed-frame-hover: var(--editor-fill-offset-10, #f4f4f4);\n --embed-frame-highlight: var(--editor-fill-offset-10, #f0f0f0);\n --embed-frame-family: var(--editor-family-sans, system-ui, sans-serif);\n }\n}\n\n.embed-frame {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--embed-frame-bg);\n color: var(--embed-frame-fg);\n font-family: var(--embed-frame-family);\n}\n\n.embed-frame .header {\n display: flex;\n align-items: center;\n gap: var(--studio-space-sm, 0.5rem);\n padding: var(--studio-space-2xs, 0.25rem) var(--studio-space-sm, 0.5rem);\n border-bottom: 1px solid var(--embed-frame-border);\n flex-shrink: 0;\n}\n\n.embed-frame .title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n font-size: 0.85em;\n cursor: default;\n}\n\n.embed-frame .title[data-renamable] {\n cursor: text;\n}\n\n.embed-frame .rename-input {\n flex: 1;\n min-width: 0;\n font: inherit;\n font-size: 0.85em;\n color: inherit;\n background: none;\n border: 1px solid var(--embed-frame-border);\n border-radius: var(--studio-radius-sm, 4px);\n padding: 0 var(--studio-space-2xs, 0.25rem);\n outline: none;\n}\n\n.embed-frame .version {\n font-size: 0.7em;\n color: var(--embed-frame-muted);\n flex-shrink: 0;\n}\n\n.embed-frame .tool-btn {\n display: inline-flex;\n align-items: center;\n height: 20px;\n padding: 0 var(--studio-space-sm, 0.5rem);\n border: 1px solid var(--embed-frame-border);\n border-radius: var(--studio-radius-round, 9999px);\n background: none;\n color: var(--embed-frame-muted);\n font: inherit;\n font-size: 0.75em;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n max-width: 10rem;\n overflow: hidden;\n text-overflow: ellipsis;\n flex-shrink: 0;\n transition: background var(--studio-transition-fast, 0.1s ease);\n}\n\n.embed-frame .tool-btn:hover {\n background: var(--embed-frame-hover);\n color: var(--embed-frame-fg);\n}\n\n.embed-frame .open-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border: none;\n background: none;\n color: inherit;\n padding: 0;\n cursor: pointer;\n opacity: 0.7;\n flex-shrink: 0;\n}\n\n.embed-frame .open-btn:hover {\n opacity: 1;\n}\n\n.embed-frame .content {\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.embed-frame .content > patchwork-view {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.embed-frame .tool-menu[popover] {\n position: fixed;\n inset: unset;\n margin: 0;\n padding: 0;\n min-width: 180px;\n border: 1px solid var(--embed-frame-border);\n border-radius: var(--studio-radius-sm, 4px);\n background: var(--embed-frame-bg);\n color: var(--embed-frame-fg);\n box-shadow: var(--studio-shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.08));\n overflow: hidden;\n font-family: var(--embed-frame-family);\n font-size: 0.85em;\n}\n\n.embed-frame .tool-menu .list {\n max-height: 200px;\n overflow-y: auto;\n padding: var(--studio-space-2xs, 0.25rem);\n}\n\n.embed-frame .tool-menu .item {\n display: block;\n width: 100%;\n padding: var(--studio-space-2xs, 0.25rem) var(--studio-space-sm, 0.5rem);\n border: none;\n border-radius: var(--studio-radius-sm, 4px);\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.embed-frame .tool-menu .item:hover {\n background: var(--embed-frame-highlight);\n}\n\n.embed-frame .tool-menu .item[data-current] {\n font-weight: 600;\n}"));
7
+ document.head.appendChild(elementStyle);
8
+ }
9
+ } catch (e) {
10
+ console.error("vite-plugin-css-injected-by-js", e);
11
+ }
12
+ })();
13
+ import { render, createComponent, insert, effect, setAttribute, use, template, delegateEvents } from "solid-js/web";
14
+ import { getRegistry, isLoadedPlugin, getFallbackTool, getSupportedToolsForType } from "@inkandswitch/patchwork-plugins";
15
+ import { getType } from "@inkandswitch/patchwork-filesystem";
16
+ import { openDocument } from "@inkandswitch/patchwork-elements";
17
+ import { createSignal, onCleanup, createEffect, Show, For } from "solid-js";
18
+ const version = "0.0.2";
19
+ var _tmpl$ = /* @__PURE__ */ template(`<span class=title>`), _tmpl$2 = /* @__PURE__ */ template(`<div class=list>`), _tmpl$3 = /* @__PURE__ */ template(`<div class=embed-frame><div class=header><span class=version>v</span><button class=tool-btn title="Open with…"></button><button class=open-btn title="Open document"></button></div><div class=content></div><div class=tool-menu popover=auto>`), _tmpl$4 = /* @__PURE__ */ template(`<input class=rename-input>`), _tmpl$5 = /* @__PURE__ */ template(`<button class=item>`);
20
+ const EMBED_TOOL_ID = "embed";
21
+ const EmbedFrame = (handle, element) => render(() => createComponent(Frame, {
22
+ handle,
23
+ element
24
+ }), element);
25
+ function Frame(props) {
26
+ var _a;
27
+ const {
28
+ handle,
29
+ element
30
+ } = props;
31
+ const [pinnedToolId, setPinnedToolId] = createSignal(element.getAttribute("embed-tool-id") || ((_a = element.closest("patchwork-view")) == null ? void 0 : _a.getAttribute("embed-tool-id")) || null);
32
+ const [doc, setDoc] = createSignal(handle.doc(), {
33
+ equals: false
34
+ });
35
+ const [datatype, setDatatype] = createSignal(null);
36
+ const [renaming, setRenaming] = createSignal(false);
37
+ const [menuOpen, setMenuOpen] = createSignal(false);
38
+ const onDocChange = () => setDoc(handle.doc());
39
+ handle.on("change", onDocChange);
40
+ onCleanup(() => handle.off("change", onDocChange));
41
+ void loadDatatype();
42
+ let menuEl;
43
+ let toolButtonEl;
44
+ const title = () => {
45
+ var _a2;
46
+ try {
47
+ return ((_a2 = datatype()) == null ? void 0 : _a2.getTitle(doc())) || "Untitled";
48
+ } catch {
49
+ return "Untitled";
50
+ }
51
+ };
52
+ const canRename = () => {
53
+ var _a2;
54
+ return Boolean((_a2 = datatype()) == null ? void 0 : _a2.setTitle);
55
+ };
56
+ const effectiveToolId = () => {
57
+ var _a2;
58
+ datatype();
59
+ if (pinnedToolId()) return pinnedToolId();
60
+ try {
61
+ return ((_a2 = getFallbackTool(doc())) == null ? void 0 : _a2.id) ?? null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ };
66
+ const toolName = () => {
67
+ const toolId = effectiveToolId();
68
+ if (!toolId) return "";
69
+ const tool = getRegistry("patchwork:tool").get(toolId);
70
+ return (tool == null ? void 0 : tool.name) ?? toolId;
71
+ };
72
+ function setTool(toolId) {
73
+ if (!toolId || toolId === pinnedToolId()) return;
74
+ setPinnedToolId(toolId);
75
+ element.dispatchEvent(new CustomEvent("patchwork:embed-tool-changed", {
76
+ detail: {
77
+ url: handle.url,
78
+ toolId
79
+ },
80
+ bubbles: true,
81
+ composed: true
82
+ }));
83
+ }
84
+ function finishRename(commit, value) {
85
+ if (!renaming()) return;
86
+ setRenaming(false);
87
+ const trimmed = value.trim();
88
+ if (commit && trimmed && trimmed !== title()) {
89
+ handle.change((d) => datatype().setTitle(d, trimmed));
90
+ }
91
+ }
92
+ function toggleMenu() {
93
+ if (menuEl.matches(":popover-open")) {
94
+ menuEl.hidePopover();
95
+ return;
96
+ }
97
+ setMenuOpen(true);
98
+ const rect = toolButtonEl.getBoundingClientRect();
99
+ const menuWidth = Math.max(rect.width, 180);
100
+ menuEl.style.top = `${rect.bottom + 2}px`;
101
+ menuEl.style.minWidth = `${menuWidth}px`;
102
+ if (rect.left + menuWidth > window.innerWidth - 8) {
103
+ menuEl.style.left = "";
104
+ menuEl.style.right = `${window.innerWidth - rect.right}px`;
105
+ } else {
106
+ menuEl.style.right = "";
107
+ menuEl.style.left = `${rect.left}px`;
108
+ }
109
+ menuEl.showPopover();
110
+ }
111
+ function listTools() {
112
+ const d = doc();
113
+ const type = d && getType(d);
114
+ if (!type) return [];
115
+ const list = getSupportedToolsForType(type).filter((t) => !t.unlisted && !t.forTitleBar && t.id !== EMBED_TOOL_ID);
116
+ list.sort((a, b) => Number(isWildcard(a)) - Number(isWildcard(b)));
117
+ return list;
118
+ }
119
+ const nestedView = document.createElement("patchwork-view");
120
+ nestedView.setAttribute("doc-url", handle.url);
121
+ createEffect(() => {
122
+ const toolId = pinnedToolId();
123
+ if (toolId) nestedView.setAttribute("tool-id", toolId);
124
+ else nestedView.removeAttribute("tool-id");
125
+ });
126
+ async function loadDatatype() {
127
+ const d = handle.doc();
128
+ const type = d && getType(d);
129
+ if (!type) return;
130
+ const registry = getRegistry("patchwork:datatype");
131
+ try {
132
+ await registry.load(type);
133
+ } catch {
134
+ }
135
+ const loaded = registry.get(type);
136
+ if (loaded && isLoadedPlugin(loaded)) {
137
+ setDatatype(() => loaded.module);
138
+ }
139
+ }
140
+ return (() => {
141
+ var _el$ = _tmpl$3(), _el$2 = _el$.firstChild, _el$4 = _el$2.firstChild;
142
+ _el$4.firstChild;
143
+ var _el$6 = _el$4.nextSibling, _el$7 = _el$6.nextSibling, _el$8 = _el$2.nextSibling, _el$9 = _el$8.nextSibling;
144
+ insert(_el$2, createComponent(Show, {
145
+ get when() {
146
+ return !renaming();
147
+ },
148
+ get fallback() {
149
+ return (() => {
150
+ var _el$1 = _tmpl$4();
151
+ _el$1.addEventListener("blur", (e) => finishRename(true, e.currentTarget.value));
152
+ _el$1.$$keydown = (e) => {
153
+ e.stopPropagation();
154
+ if (e.key === "Enter") finishRename(true, e.currentTarget.value);
155
+ if (e.key === "Escape") finishRename(false, e.currentTarget.value);
156
+ };
157
+ use((el) => queueMicrotask(() => {
158
+ el.focus();
159
+ el.select();
160
+ }), _el$1);
161
+ effect(() => _el$1.value = title());
162
+ return _el$1;
163
+ })();
164
+ },
165
+ get children() {
166
+ var _el$3 = _tmpl$();
167
+ _el$3.$$click = () => canRename() && setRenaming(true);
168
+ insert(_el$3, title);
169
+ effect((_p$) => {
170
+ var _v$ = canRename() ? "" : void 0, _v$2 = canRename() ? "Click to rename" : void 0;
171
+ _v$ !== _p$.e && setAttribute(_el$3, "data-renamable", _p$.e = _v$);
172
+ _v$2 !== _p$.t && setAttribute(_el$3, "title", _p$.t = _v$2);
173
+ return _p$;
174
+ }, {
175
+ e: void 0,
176
+ t: void 0
177
+ });
178
+ return _el$3;
179
+ }
180
+ }), _el$4);
181
+ insert(_el$4, version, null);
182
+ _el$6.$$click = toggleMenu;
183
+ var _ref$ = toolButtonEl;
184
+ typeof _ref$ === "function" ? use(_ref$, _el$6) : toolButtonEl = _el$6;
185
+ insert(_el$6, toolName);
186
+ _el$7.$$click = () => openDocument(element, handle.url, pinnedToolId() || void 0);
187
+ _el$7.innerHTML = openIcon;
188
+ insert(_el$8, nestedView);
189
+ use((el) => {
190
+ menuEl = el;
191
+ el.addEventListener("toggle", (e) => {
192
+ if (e.newState === "closed") {
193
+ setMenuOpen(false);
194
+ }
195
+ });
196
+ }, _el$9);
197
+ insert(_el$9, createComponent(Show, {
198
+ get when() {
199
+ return menuOpen();
200
+ },
201
+ get children() {
202
+ var _el$0 = _tmpl$2();
203
+ insert(_el$0, createComponent(For, {
204
+ get each() {
205
+ return listTools();
206
+ },
207
+ children: (tool) => (() => {
208
+ var _el$10 = _tmpl$5();
209
+ _el$10.$$click = () => {
210
+ menuEl.hidePopover();
211
+ setTool(tool.id);
212
+ };
213
+ insert(_el$10, () => tool.name || tool.id);
214
+ effect(() => setAttribute(_el$10, "data-current", tool.id === effectiveToolId() ? "" : void 0));
215
+ return _el$10;
216
+ })()
217
+ }));
218
+ return _el$0;
219
+ }
220
+ }));
221
+ return _el$;
222
+ })();
223
+ }
224
+ function isWildcard(tool) {
225
+ return tool.supportedDatatypes === "*" || Array.isArray(tool.supportedDatatypes) && tool.supportedDatatypes.includes("*");
226
+ }
227
+ const openIcon = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
228
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
229
+ <polyline points="15 3 21 3 21 9"></polyline>
230
+ <line x1="10" y1="14" x2="21" y2="3"></line>
231
+ </svg>`;
232
+ delegateEvents(["click", "keydown"]);
233
+ export {
234
+ EmbedFrame as default
235
+ };
236
+ //# sourceMappingURL=tool-Cg7zrpH9.js.map