@inkandswitch/patchwork-pkg-base 0.0.23 → 0.0.24
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 +1 -0
- package/package.json +1 -1
- package/packages/codemirror-markdown/dist/extensions/embed.js +42 -107
- package/packages/codemirror-markdown/dist/extensions/embed.js.map +3 -3
- package/packages/embed/dist/assets/tool-Cg7zrpH9.js +236 -0
- package/packages/embed/dist/assets/tool-Cg7zrpH9.js.map +1 -0
- package/packages/embed/dist/index.js +94 -0
- package/packages/embed/dist/index.js.map +1 -0
- package/packages/embed/package.json +27 -0
- package/packages/tldraw4/dist/{chunk-VF5CY24G.js → chunk-GDYMD4VT.js} +146 -146
- package/packages/tldraw4/dist/{chunk-VF5CY24G.js.map → chunk-GDYMD4VT.js.map} +4 -4
- package/packages/tldraw4/dist/datatype.js +1 -1
- package/packages/tldraw4/dist/tool.js +75 -383
- package/packages/tldraw4/dist/tool.js.map +3 -3
package/modules.json
CHANGED
package/package.json
CHANGED
|
@@ -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-
|
|
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`
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
const dt = event.dataTransfer;
|
|
211
|
+
if (!wantsDragover(dt)) return false;
|
|
281
212
|
event.preventDefault();
|
|
282
|
-
if (
|
|
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.
|
|
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"
|
|
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": ";AAAA;AAAA,EACE,cAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-Cg7zrpH9.js","sources":["../../src/tool.tsx"],"sourcesContent":["// Embed frame: chrome (title, tool picker, open button) around a nested\n// <patchwork-view>. Hosts mount it via <patchwork-view tool-id=\"embed\">, pin\n// the inner tool with the `embed-tool-id` attribute, and persist picks by\n// listening for the bubbling `patchwork:embed-tool-changed` event.\n\nimport {\n getFallbackTool,\n getRegistry,\n getSupportedToolsForType,\n isLoadedPlugin,\n type DatatypeImplementation,\n type ToolDescription,\n type ToolImplementation,\n} from \"@inkandswitch/patchwork-plugins\";\nimport { getType } from \"@inkandswitch/patchwork-filesystem\";\nimport { openDocument } from \"@inkandswitch/patchwork-elements\";\nimport type { DocHandle } from \"@automerge/automerge-repo\";\nimport { createEffect, createSignal, For, onCleanup, Show } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { version } from \"../package.json\";\nimport \"./embed.css\";\n\nconst EMBED_TOOL_ID = \"embed\";\n\nconst EmbedFrame: ToolImplementation<any> = (handle, element) =>\n render(() => <Frame handle={handle} element={element} />, element);\n\nexport default EmbedFrame;\n\ntype FrameProps = {\n handle: DocHandle<any>;\n element: HTMLElement;\n};\n\nfunction Frame(props: FrameProps) {\n // Static per mount (the render contract passes them once); safe to unwrap.\n const { handle, element } = props;\n\n // `element` is the inner <patchwork-view-legacy>; hosts set `embed-tool-id`\n // on the outer <patchwork-view>, so check there too.\n const [pinnedToolId, setPinnedToolId] = createSignal(\n element.getAttribute(\"embed-tool-id\") ||\n element.closest(\"patchwork-view\")?.getAttribute(\"embed-tool-id\") ||\n null\n );\n const [doc, setDoc] = createSignal<any>(handle.doc(), { equals: false });\n const [datatype, setDatatype] =\n createSignal<DatatypeImplementation<any> | null>(null);\n const [renaming, setRenaming] = createSignal(false);\n const [menuOpen, setMenuOpen] = createSignal(false);\n\n const onDocChange = () => setDoc(handle.doc());\n handle.on(\"change\", onDocChange);\n onCleanup(() => handle.off(\"change\", onDocChange));\n void loadDatatype();\n\n let menuEl!: HTMLDivElement;\n let toolButtonEl!: HTMLButtonElement;\n\n const title = () => {\n try {\n return datatype()?.getTitle(doc()) || \"Untitled\";\n } catch {\n // getTitle throws on unexpected doc shapes\n return \"Untitled\";\n }\n };\n\n const canRename = () => Boolean(datatype()?.setTitle);\n\n // With no pinned tool the nested view renders the doc's fallback tool, so\n // surface that as the selection rather than a special \"unselected\" state.\n const effectiveToolId = () => {\n datatype(); // re-check once the datatype (and the tools it ships) loaded\n if (pinnedToolId()) return pinnedToolId();\n try {\n return getFallbackTool(doc())?.id ?? null;\n } catch {\n return null;\n }\n };\n\n const toolName = () => {\n const toolId = effectiveToolId();\n if (!toolId) return \"\";\n const tool = getRegistry<ToolDescription>(\"patchwork:tool\").get(toolId);\n return tool?.name ?? toolId;\n };\n\n function setTool(toolId: string): void {\n if (!toolId || toolId === pinnedToolId()) return;\n setPinnedToolId(toolId);\n element.dispatchEvent(\n new CustomEvent(\"patchwork:embed-tool-changed\", {\n detail: { url: handle.url, toolId },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n function finishRename(commit: boolean, value: string): void {\n if (!renaming()) return;\n setRenaming(false);\n const trimmed = value.trim();\n if (commit && trimmed && trimmed !== title()) {\n handle.change((d: any) => datatype()!.setTitle!(d, trimmed));\n }\n }\n\n function toggleMenu(): void {\n if (menuEl.matches(\":popover-open\")) {\n menuEl.hidePopover();\n return;\n }\n setMenuOpen(true);\n const rect = toolButtonEl.getBoundingClientRect();\n const menuWidth = Math.max(rect.width, 180);\n menuEl.style.top = `${rect.bottom + 2}px`;\n menuEl.style.minWidth = `${menuWidth}px`;\n if (rect.left + menuWidth > window.innerWidth - 8) {\n menuEl.style.left = \"\";\n menuEl.style.right = `${window.innerWidth - rect.right}px`;\n } else {\n menuEl.style.right = \"\";\n menuEl.style.left = `${rect.left}px`;\n }\n menuEl.showPopover();\n }\n\n function listTools(): ToolDescription[] {\n const d = doc();\n const type = d && getType(d);\n if (!type) return [];\n const list = getSupportedToolsForType(type).filter(\n (t) => !t.unlisted && !t.forTitleBar && t.id !== EMBED_TOOL_ID\n );\n // Datatype-specific tools before the generic wildcard ones.\n list.sort((a, b) => Number(isWildcard(a)) - Number(isWildcard(b)));\n return list;\n }\n\n // Built imperatively rather than as JSX so we don't have to teach Solid's\n // JSX types about the custom element.\n const nestedView = document.createElement(\"patchwork-view\");\n nestedView.setAttribute(\"doc-url\", handle.url);\n createEffect(() => {\n const toolId = pinnedToolId();\n if (toolId) nestedView.setAttribute(\"tool-id\", toolId);\n else nestedView.removeAttribute(\"tool-id\");\n });\n\n // Load the datatype (for getTitle/setTitle and its tools).\n async function loadDatatype(): Promise<void> {\n const d = handle.doc();\n const type = d && getType(d);\n if (!type) return;\n const registry = getRegistry(\"patchwork:datatype\");\n try {\n await registry.load(type);\n } catch {\n // datatype unavailable; keep the fallbacks\n }\n const loaded = registry.get(type);\n if (loaded && isLoadedPlugin(loaded)) {\n setDatatype(() => loaded.module as DatatypeImplementation<any>);\n }\n }\n\n return (\n <div class=\"embed-frame\">\n <div class=\"header\">\n <Show\n when={!renaming()}\n fallback={\n <input\n class=\"rename-input\"\n value={title()}\n ref={(el) =>\n queueMicrotask(() => {\n el.focus();\n el.select();\n })\n }\n onKeyDown={(e) => {\n e.stopPropagation();\n if (e.key === \"Enter\") finishRename(true, e.currentTarget.value);\n if (e.key === \"Escape\")\n finishRename(false, e.currentTarget.value);\n }}\n onBlur={(e) => finishRename(true, e.currentTarget.value)}\n />\n }\n >\n <span\n class=\"title\"\n data-renamable={canRename() ? \"\" : undefined}\n title={canRename() ? \"Click to rename\" : undefined}\n onClick={() => canRename() && setRenaming(true)}\n >\n {title()}\n </span>\n </Show>\n {/* Deploy check: which build is actually running. */}\n <span class=\"version\">v{version}</span>\n <button\n class=\"tool-btn\"\n title={\"Open with\\u2026\"}\n ref={toolButtonEl}\n onClick={toggleMenu}\n >\n {toolName()}\n </button>\n <button\n class=\"open-btn\"\n title=\"Open document\"\n innerHTML={openIcon}\n onClick={() =>\n openDocument(element, handle.url, pinnedToolId() || undefined)\n }\n />\n </div>\n <div class=\"content\">{nestedView}</div>\n <div\n class=\"tool-menu\"\n popover=\"auto\"\n ref={(el) => {\n menuEl = el;\n // Unrender the item list while closed.\n el.addEventListener(\"toggle\", (e) => {\n if ((e as { newState?: string }).newState === \"closed\") {\n setMenuOpen(false);\n }\n });\n }}\n >\n <Show when={menuOpen()}>\n <div class=\"list\">\n <For each={listTools()}>\n {(tool) => (\n <button\n class=\"item\"\n data-current={tool.id === effectiveToolId() ? \"\" : undefined}\n onClick={() => {\n menuEl.hidePopover();\n setTool(tool.id);\n }}\n >\n {tool.name || tool.id}\n </button>\n )}\n </For>\n </div>\n </Show>\n </div>\n </div>\n );\n}\n\nfunction isWildcard(tool: ToolDescription): boolean {\n return (\n tool.supportedDatatypes === \"*\" ||\n (Array.isArray(tool.supportedDatatypes) &&\n tool.supportedDatatypes.includes(\"*\"))\n );\n}\n\nconst openIcon = `<svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n\t<path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>\n\t<polyline points=\"15 3 21 3 21 9\"></polyline>\n\t<line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"></line>\n</svg>`;\n"],"names":["EMBED_TOOL_ID","EmbedFrame","handle","element","render","_$createComponent","Frame","props","pinnedToolId","setPinnedToolId","createSignal","getAttribute","closest","doc","setDoc","equals","datatype","setDatatype","renaming","setRenaming","menuOpen","setMenuOpen","onDocChange","on","onCleanup","off","loadDatatype","menuEl","toolButtonEl","title","getTitle","canRename","Boolean","setTitle","effectiveToolId","getFallbackTool","id","toolName","toolId","tool","getRegistry","get","name","setTool","dispatchEvent","CustomEvent","detail","url","bubbles","composed","finishRename","commit","value","trimmed","trim","change","d","toggleMenu","matches","hidePopover","rect","getBoundingClientRect","menuWidth","Math","max","width","style","top","bottom","minWidth","left","window","innerWidth","right","showPopover","listTools","type","getType","list","getSupportedToolsForType","filter","t","unlisted","forTitleBar","sort","a","b","Number","isWildcard","nestedView","document","createElement","setAttribute","createEffect","removeAttribute","registry","load","loaded","isLoadedPlugin","module","_el$","_tmpl$3","_el$2","firstChild","_el$4","_el$6","nextSibling","_el$7","_el$8","_el$9","_$insert","Show","when","fallback","_el$1","_tmpl$4","addEventListener","e","currentTarget","$$keydown","stopPropagation","key","_$use","el","queueMicrotask","focus","select","_$effect","children","_el$3","_tmpl$","$$click","_p$","_v$","undefined","_v$2","_$setAttribute","version","_ref$","openDocument","innerHTML","openIcon","newState","_el$0","_tmpl$2","For","each","_el$10","_tmpl$5","supportedDatatypes","Array","isArray","includes","_$delegateEvents"],"mappings":"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"}
|