@inkandswitch/patchwork-pkg-base 0.0.119 → 0.0.121
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/module-settings-url +1 -1
- package/package.json +1 -1
- package/packages/codemirror-markdown/dist/extensions/embed.js +61 -19
- package/packages/codemirror-markdown/dist/extensions/embed.js.map +3 -3
- package/packages/codemirror-markdown/dist/extensions/links.js +106 -13
- package/packages/codemirror-markdown/dist/extensions/links.js.map +3 -3
- package/packages/codemirror-markdown/dist/extensions/markdown.js +8 -0
- package/packages/codemirror-markdown/dist/extensions/markdown.js.map +2 -2
- package/plugin-registry-url +1 -1
package/module-settings-url
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
automerge:
|
|
1
|
+
automerge:23J7DGQRcwEus8TKfgXca2k4Roc
|
package/package.json
CHANGED
|
@@ -111,49 +111,91 @@ function isImageDoc(doc) {
|
|
|
111
111
|
if (!(doc.content instanceof Uint8Array)) return false;
|
|
112
112
|
return !!doc.mimeType?.startsWith("image/") || IMAGE_EXTENSIONS.includes(doc.extension?.toLowerCase() ?? "");
|
|
113
113
|
}
|
|
114
|
+
var seen = /* @__PURE__ */ new Map();
|
|
115
|
+
function imageFor(docId, doc) {
|
|
116
|
+
const prev = seen.get(docId);
|
|
117
|
+
if (prev?.kind === "image" && prev.content === doc.content) return prev;
|
|
118
|
+
if (prev?.kind === "image") URL.revokeObjectURL(prev.url);
|
|
119
|
+
const content = doc.content;
|
|
120
|
+
const next = {
|
|
121
|
+
kind: "image",
|
|
122
|
+
content,
|
|
123
|
+
url: URL.createObjectURL(
|
|
124
|
+
new Blob([content], { type: doc.mimeType || "" })
|
|
125
|
+
),
|
|
126
|
+
name: doc.name ?? ""
|
|
127
|
+
};
|
|
128
|
+
seen.set(docId, next);
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
114
131
|
function mountImageOrFrame(docId, container, mountFrame) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
132
|
+
const known = seen.get(docId);
|
|
133
|
+
if (known?.kind === "frame") {
|
|
117
134
|
mountFrame();
|
|
118
135
|
return () => {
|
|
119
136
|
};
|
|
120
137
|
}
|
|
138
|
+
const repo = window.repo;
|
|
121
139
|
let disposed = false;
|
|
122
|
-
let objectUrl;
|
|
123
140
|
let handle;
|
|
124
141
|
let img;
|
|
142
|
+
const show = (image) => {
|
|
143
|
+
if (!img) {
|
|
144
|
+
container.classList.add("cm-embed-image");
|
|
145
|
+
img = document.createElement("img");
|
|
146
|
+
img.addEventListener("load", () => {
|
|
147
|
+
const current = seen.get(docId);
|
|
148
|
+
if (current?.kind !== "image" || img.src !== current.url) return;
|
|
149
|
+
current.width = img.naturalWidth;
|
|
150
|
+
current.height = img.naturalHeight;
|
|
151
|
+
img.width = current.width;
|
|
152
|
+
img.height = current.height;
|
|
153
|
+
});
|
|
154
|
+
container.appendChild(img);
|
|
155
|
+
}
|
|
156
|
+
if (img.getAttribute("src") === image.url) return;
|
|
157
|
+
if (image.width && image.height) {
|
|
158
|
+
img.width = image.width;
|
|
159
|
+
img.height = image.height;
|
|
160
|
+
} else {
|
|
161
|
+
img.removeAttribute("width");
|
|
162
|
+
img.removeAttribute("height");
|
|
163
|
+
}
|
|
164
|
+
img.alt = image.name;
|
|
165
|
+
img.src = image.url;
|
|
166
|
+
};
|
|
125
167
|
const render = () => {
|
|
126
168
|
const doc = handle.doc();
|
|
127
|
-
if (
|
|
128
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
129
|
-
objectUrl = URL.createObjectURL(
|
|
130
|
-
new Blob([doc.content], { type: doc.mimeType || "" })
|
|
131
|
-
);
|
|
132
|
-
img.src = objectUrl;
|
|
133
|
-
img.alt = doc.name ?? "";
|
|
169
|
+
if (isImageDoc(doc)) show(imageFor(docId, doc));
|
|
134
170
|
};
|
|
171
|
+
if (known?.kind === "image") show(known);
|
|
172
|
+
if (!repo?.find) {
|
|
173
|
+
if (!img) mountFrame();
|
|
174
|
+
return () => {
|
|
175
|
+
};
|
|
176
|
+
}
|
|
135
177
|
Promise.resolve(repo.find(`automerge:${docId}`)).then(
|
|
136
178
|
(h) => {
|
|
137
179
|
if (disposed) return;
|
|
138
180
|
handle = h;
|
|
139
|
-
|
|
181
|
+
const doc = handle.doc();
|
|
182
|
+
if (!isImageDoc(doc)) {
|
|
183
|
+
if (doc) seen.set(docId, { kind: "frame" });
|
|
184
|
+
img?.remove();
|
|
185
|
+
container.classList.remove("cm-embed-image");
|
|
140
186
|
mountFrame();
|
|
141
187
|
return;
|
|
142
188
|
}
|
|
143
|
-
container.classList.add("cm-embed-image");
|
|
144
|
-
img = document.createElement("img");
|
|
145
|
-
container.appendChild(img);
|
|
146
189
|
render();
|
|
147
190
|
handle.on("change", render);
|
|
148
191
|
},
|
|
149
192
|
() => {
|
|
150
|
-
if (!disposed) mountFrame();
|
|
193
|
+
if (!disposed && !img) mountFrame();
|
|
151
194
|
}
|
|
152
195
|
);
|
|
153
196
|
return () => {
|
|
154
197
|
disposed = true;
|
|
155
198
|
handle?.off?.("change", render);
|
|
156
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
157
199
|
};
|
|
158
200
|
}
|
|
159
201
|
var EMBED_PATTERN = /\[patchwork:([^/\]]+)(?:\/([^\]]+))?\]/g;
|
|
@@ -190,10 +232,10 @@ function urlToDocId(raw) {
|
|
|
190
232
|
}
|
|
191
233
|
function extractDocRefs(dt) {
|
|
192
234
|
const refs = [];
|
|
193
|
-
const
|
|
235
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
194
236
|
const push = (docId, toolId) => {
|
|
195
|
-
if (!docId ||
|
|
196
|
-
|
|
237
|
+
if (!docId || seen2.has(docId)) return;
|
|
238
|
+
seen2.add(docId);
|
|
197
239
|
refs.push({ docId, toolId });
|
|
198
240
|
};
|
|
199
241
|
const dnd = dt.getData(PATCHWORK_DND);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/extensions/embed.ts", "../../src/themes/embed.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n dropCursor,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport { Range } from \"@codemirror/state\";\nimport {\n type AutomergeUrl,\n type DocumentId,\n isValidDocumentId,\n parseAutomergeUrl,\n} from \"@automerge/automerge-repo/slim\";\nimport { embedTheme } from \"../themes/embed.ts\";\n\n/**\n * Renders an embedded doc through the shared \"embed\" tool, which draws the\n * chrome and nests the content view. The inner tool travels via the\n * `embed-tool-id` attribute; tool picks come back as\n * `patchwork:embed-tool-changed` events and are persisted by rewriting the\n * marker (which recreates the widget).\n */\nclass EmbedWidget extends WidgetType {\n readonly docId: DocumentId;\n // `null` = no pinned tool; the embed frame uses the datatype's fallback.\n readonly toolId: string | null;\n readonly embedText: string;\n\n constructor(docId: DocumentId, toolId: string | null, embedText: string) {\n super();\n this.docId = docId;\n this.toolId = toolId;\n this.embedText = embedText;\n }\n\n eq(other: EmbedWidget) {\n return other.docId === this.docId && other.toolId === this.toolId;\n }\n\n toDOM(view: EditorView) {\n const container = document.createElement(\"div\");\n container.className = \"cm-embed\";\n\n // An image with no pinned tool is just drawn, with no frame round it. We\n // can't tell until the doc loads, so hold off mounting the frame until then.\n if (this.toolId) {\n this.mountFrame(view, container);\n } else {\n const cleanup = mountImageOrFrame(this.docId, container, () =>\n this.mountFrame(view, container)\n );\n cleanups.set(container, cleanup);\n }\n return container;\n }\n\n destroy(dom: HTMLElement) {\n cleanups.get(dom)?.();\n cleanups.delete(dom);\n }\n\n private mountFrame(view: EditorView, container: HTMLElement): void {\n const patchworkView = document.createElement(\"patchwork-view\");\n // No heads in the url: the embed follows whatever version the host\n // resolves \u2014 the active checkpoint when one is checked out, live otherwise.\n patchworkView.setAttribute(\"doc-url\", `automerge:${this.docId}`);\n patchworkView.setAttribute(\"tool-id\", \"embed\");\n if (this.toolId) patchworkView.setAttribute(\"embed-tool-id\", this.toolId);\n // Explicit inline height: without it the element collapses to 0px (the\n // stylesheet rule isn't reliably applied here).\n patchworkView.style.display = \"block\";\n patchworkView.style.height = \"500px\";\n patchworkView.style.width = \"100%\";\n\n // Persist tool picks into the marker text. Stop propagation so nested\n // embeds don't also change the outer embed's tool.\n container.addEventListener(\"patchwork:embed-tool-changed\", (e) => {\n e.stopPropagation();\n const toolId = (e as CustomEvent<{ toolId?: string }>).detail?.toolId;\n if (toolId) this.setTool(view, container, toolId);\n });\n\n container.appendChild(patchworkView);\n }\n\n private setTool(\n view: EditorView,\n container: HTMLElement,\n toolId: string\n ): void {\n const from = view.posAtDOM(container);\n const to = from + this.embedText.length;\n // Only rewrite if the marker is still exactly where this widget maps to.\n if (view.state.doc.sliceString(from, to) !== this.embedText) return;\n view.dispatch({\n changes: { from, to, insert: embedSyntax({ docId: this.docId, toolId }) },\n });\n }\n\n ignoreEvent() {\n // Atomic embed: the editor ignores all events; the frame handles them.\n return true;\n }\n}\n\nconst cleanups = new WeakMap<HTMLElement, () => void>();\n\nconst IMAGE_EXTENSIONS = [\"svg\", \"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"bmp\"];\n\ntype FileDoc = {\n \"@patchwork\"?: { type?: string };\n content?: unknown;\n mimeType?: string;\n extension?: string;\n name?: string;\n};\n\nfunction isImageDoc(doc: FileDoc | undefined): boolean {\n if (doc?.[\"@patchwork\"]?.type !== \"file\") return false;\n if (!(doc.content instanceof Uint8Array)) return false;\n return (\n !!doc.mimeType?.startsWith(\"image/\") ||\n IMAGE_EXTENSIONS.includes(doc.extension?.toLowerCase() ?? \"\")\n );\n}\n\n/**\n * Look the doc up; if it's an image file, draw it as a bare `<img>` that\n * follows the doc's content. Anything else (or no `window.repo`) gets the\n * normal embed frame. Returns a cleanup for when the widget goes away.\n *\n * Reads through the realm-local `window.repo`, so an image changed only in a\n * draft shows its main-branch bytes here.\n */\nfunction mountImageOrFrame(\n docId: DocumentId,\n container: HTMLElement,\n mountFrame: () => void\n): () => void {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo?.find) {\n mountFrame();\n return () => {};\n }\n\n let disposed = false;\n let objectUrl: string | undefined;\n let handle: any;\n let img: HTMLImageElement | undefined;\n\n const render = () => {\n const doc = handle.doc() as FileDoc | undefined;\n if (!isImageDoc(doc)) return;\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n objectUrl = URL.createObjectURL(\n new Blob([doc!.content as BlobPart], { type: doc!.mimeType || \"\" })\n );\n img!.src = objectUrl;\n img!.alt = doc!.name ?? \"\";\n };\n\n Promise.resolve(repo.find(`automerge:${docId}`)).then(\n (h) => {\n if (disposed) return;\n handle = h;\n if (!isImageDoc(handle.doc())) {\n mountFrame();\n return;\n }\n container.classList.add(\"cm-embed-image\");\n img = document.createElement(\"img\");\n container.appendChild(img);\n render();\n handle.on(\"change\", render);\n },\n () => {\n if (!disposed) mountFrame();\n }\n );\n\n return () => {\n disposed = true;\n handle?.off?.(\"change\", render);\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n}\n\n// Marker syntax: [patchwork:docId] or [patchwork:docId/toolId] (ids can't\n// contain `/` or `]`). We scan plain text instead of the markdown syntax tree\n// on purpose: `@codemirror/language` isn't a shared singleton across\n// patchwork's separately-bundled extensions, so `syntaxTree(state)` here would\n// read a different `Language` facet and always come back empty.\nconst EMBED_PATTERN = /\\[patchwork:([^/\\]]+)(?:\\/([^\\]]+))?\\]/g;\n\nfunction getEmbedLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n\n // Visible ranges are line-aligned and markers never span lines, so a marker\n // is always fully inside or fully outside a range.\n for (const { from, to } of view.visibleRanges) {\n const text = state.doc.sliceString(from, to);\n EMBED_PATTERN.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = EMBED_PATTERN.exec(text)) !== null) {\n const matchFrom = from + m.index;\n const matchTo = matchFrom + m[0].length;\n const [matchText, docId, toolId] = m;\n\n if (!isValidDocumentId(docId)) continue;\n\n const embed = Decoration.replace({\n widget: new EmbedWidget(docId as DocumentId, toolId ?? null, matchText),\n });\n widgets.push(embed.range(matchFrom, matchTo));\n }\n }\n\n // `true` lets CodeMirror sort the ranges defensively (matches are already in\n // document order, but this is cheap insurance).\n return Decoration.set(widgets, true);\n}\n\n// MIME types we accept document drags from. Mirrors the sideboard's convention.\nconst PATCHWORK_DND = \"text/x-patchwork-dnd\";\nconst PATCHWORK_URLS = \"text/x-patchwork-urls\";\n\ntype DocRef = { docId: DocumentId; toolId: string | null };\n\n/** Turn an automerge url or a patchwork web link into a DocumentId, or null. */\nfunction urlToDocId(raw: string): DocumentId | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Plain automerge url (optionally carrying heads/query/subpath).\n const am = trimmed.match(/automerge:([a-zA-Z0-9]+)/);\n if (am && isValidDocumentId(am[1])) return am[1] as DocumentId;\n // Patchwork web link: #doc=<documentId> (also &doc= / ?doc=).\n const web = trimmed.match(/[#&?]doc=([a-zA-Z0-9]+)/);\n if (web && isValidDocumentId(web[1])) return web[1] as DocumentId;\n return null;\n}\n\n/**\n * Read dragged documents out of a drop event, in order of format preference.\n * Only an *explicit* `toolId` on a structured item pins the tool; otherwise we\n * embed tool-less and let the view fall back to the datatype's default tool.\n */\nfunction extractDocRefs(dt: DataTransfer): DocRef[] {\n const refs: DocRef[] = [];\n const seen = new Set<string>();\n const push = (docId: DocumentId | null, toolId: string | null) => {\n if (!docId || seen.has(docId)) return;\n seen.add(docId);\n refs.push({ docId, toolId });\n };\n\n const dnd = dt.getData(PATCHWORK_DND);\n if (dnd) {\n try {\n const parsed = JSON.parse(dnd) as {\n items?: Array<{ url?: string; toolId?: string }>;\n };\n for (const item of parsed?.items ?? []) {\n if (item?.url) push(urlToDocId(item.url), item.toolId ?? null);\n }\n } catch {\n // fall through to the other formats\n }\n }\n if (refs.length > 0) return refs;\n\n const urls = dt.getData(PATCHWORK_URLS);\n if (urls) {\n try {\n const parsed: unknown = JSON.parse(urls);\n if (Array.isArray(parsed)) {\n for (const u of parsed) push(urlToDocId(String(u)), null);\n }\n } catch {\n // fall through\n }\n }\n if (refs.length > 0) return refs;\n\n const text = dt.getData(\"text/uri-list\") || dt.getData(\"text/plain\");\n if (text) {\n for (const line of text.split(/\\r?\\n/)) {\n if (line.startsWith(\"#\")) continue; // uri-list comments\n push(urlToDocId(line), null);\n }\n }\n return refs;\n}\n\nfunction embedSyntax({ docId, toolId }: DocRef): string {\n return toolId ? `[patchwork:${docId}/${toolId}]` : `[patchwork:${docId}]`;\n}\n\n/**\n * Import each OS file (dropped from the desktop or pasted) as a Patchwork\n * `file` document, returning tool-less refs to embed. Uses the realm-local\n * `window.repo` (the documented global) \u2014 fine for creating brand-new docs,\n * which aren't subject to draft remapping.\n */\nasync function importFileRefs(files: Iterable<File>): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\"[codemirror-embed] window.repo unavailable; ignoring files\");\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of files) {\n try {\n const mimeType = file.type || \"application/octet-stream\";\n const isText =\n mimeType.startsWith(\"text/\") || mimeType === \"application/json\";\n const content = isText\n ? await file.text()\n : new Uint8Array(await file.arrayBuffer());\n const parts = file.name.split(\".\");\n const extension = parts.length > 1 ? parts.pop()! : \"\";\n const handle = repo.create();\n handle.change((d: any) => {\n d[\"@patchwork\"] = { type: \"file\" };\n d.content = content;\n d.mimeType = mimeType;\n d.extension = extension;\n d.name = file.name;\n });\n const { documentId } = parseAutomergeUrl(handle.url as AutomergeUrl);\n if (isValidDocumentId(documentId)) {\n refs.push({ docId: documentId as DocumentId, toolId: null });\n }\n } catch (err) {\n console.warn(\n \"[codemirror-embed] failed to import file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\n/** Insert embeds over `from..to` (a drop point, or a pasted-over selection). */\nfunction insertRefs(\n view: EditorView,\n from: number,\n refs: DocRef[],\n to = from\n): void {\n if (refs.length === 0) return;\n // Files import asynchronously, so the doc may have shrunk in the meantime.\n const len = view.state.doc.length;\n from = Math.min(from, len);\n to = Math.min(to, len);\n let text = refs.map(embedSyntax).join(\"\\n\\n\");\n // On a non-empty line, add just enough breaks to put the embed on its own line.\n const { doc } = view.state;\n const start = doc.lineAt(from);\n const end = doc.lineAt(to);\n if (/\\S/.test(start.text) && from > start.from) text = \"\\n\" + text;\n if (/\\S/.test(end.text) && to < end.to) text = text + \"\\n\";\n view.dispatch({\n changes: { from, to, insert: text },\n selection: { anchor: from + text.length },\n });\n}\n\n/**\n * Drop handler that accepts:\n * - documents dragged from the sidebar / canvases / other tools\n * (`text/x-patchwork-dnd`, `text/x-patchwork-urls`, `text/uri-list`, and\n * `text/plain` patchwork links), and\n * - files dragged in from the operating system (imported as `file` docs).\n * Inserts `[patchwork:docId]` (or `[patchwork:docId/toolId]`) at the drop point.\n */\nfunction embedDropHandlers() {\n // Only claim the *dragover* for unambiguous patchwork/OS drags \u2014 plain\n // text/uri-list can be an ordinary in-editor text drag, which we must not\n // swallow. (`Files` covers OS drags, whose `dt.files` is empty until drop.)\n const wantsDragover = (dt: DataTransfer | null): boolean =>\n !!dt &&\n (dt.types.includes(\"Files\") ||\n dt.types.includes(PATCHWORK_DND) ||\n dt.types.includes(PATCHWORK_URLS));\n\n return EditorView.domEventHandlers({\n dragover(event) {\n const dt = event.dataTransfer;\n if (!wantsDragover(dt)) return false;\n event.preventDefault();\n // Doc drags insert a *reference* to the same automerge doc, so show the\n // link cursor; OS file drags genuinely copy content into new docs.\n if (dt) dt.dropEffect = dt.types.includes(\"Files\") ? \"copy\" : \"link\";\n return true;\n },\n drop(event, view) {\n const dt = event.dataTransfer;\n if (!dt) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n\n // OS files import asynchronously; insert once the docs exist.\n if (dt.files && dt.files.length > 0) {\n event.preventDefault();\n void importFileRefs(Array.from(dt.files)).then((refs) =>\n insertRefs(view, pos, refs)\n );\n return true;\n }\n\n // Otherwise only handle the drop if it actually resolves to patchwork\n // docs (dnd/urls always do; uri-list/plain only for patchwork links).\n // If not, let CodeMirror handle it as a normal text drop.\n const refs = extractDocRefs(dt);\n if (refs.length === 0) return false;\n event.preventDefault();\n insertRefs(view, pos, refs);\n return true;\n },\n paste(event, view) {\n // Image pastes (screenshots, \"Copy Image\") become embedded `file` docs.\n // A paste that also carries text is left to CodeMirror: rich text from\n // office apps attaches a picture of itself, and a Finder copy carries\n // the file's icon alongside its name.\n const dt = event.clipboardData;\n if (!dt || dt.getData(\"text/plain\")) return false;\n const images = Array.from(dt.files ?? []).filter((f) =>\n f.type.startsWith(\"image/\")\n );\n if (images.length === 0) return false;\n event.preventDefault();\n const { from, to } = view.state.selection.main;\n void importFileRefs(images).then((refs) =>\n insertRefs(view, from, refs, to)\n );\n return true;\n },\n });\n}\n\nconst embedPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getEmbedLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Viewport too, so newly-visible markers get decorated.\n if (update.docChanged || update.viewportChanged) {\n this.decorations = getEmbedLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n // Atomic: the cursor skips embeds and delete removes the whole marker.\n provide: (plugin) =>\n EditorView.atomicRanges.of(\n (view) => view.plugin(plugin)?.decorations ?? Decoration.none\n ),\n }\n);\n\nexport function markdownEmbed() {\n // `dropCursor` observes events, so it coexists with our dragover handler.\n return [embedPlugin, embedTheme, embedDropHandlers(), dropCursor()];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\n// Outer container only; all embed chrome lives in the shared \"embed\" tool.\nexport const embedTheme = EditorView.baseTheme({\n \".cm-embed\": {\n display: \"block\",\n border: \"var(--studio-border-width, 1px) var(--studio-border-style, solid)\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n },\n \"&light .cm-embed\": {\n borderColor: \"#ddd\",\n },\n \"&dark .cm-embed\": {\n borderColor: \"#333\",\n },\n // Images are just images: no border, natural size, never wider than the text.\n \"&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image\": {\n border: \"none\",\n borderRadius: \"0\",\n },\n \".cm-embed-image > img\": {\n display: \"block\",\n maxWidth: \"100%\",\n height: \"auto\",\n },\n \".cm-embed > patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n});\n"],
|
|
5
|
-
"mappings": "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
|
|
6
|
-
"names": ["EditorView", "EditorView", "refs"]
|
|
4
|
+
"sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n dropCursor,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport { Range } from \"@codemirror/state\";\nimport {\n type AutomergeUrl,\n type DocumentId,\n isValidDocumentId,\n parseAutomergeUrl,\n} from \"@automerge/automerge-repo/slim\";\nimport { embedTheme } from \"../themes/embed.ts\";\n\n/**\n * Renders an embedded doc through the shared \"embed\" tool, which draws the\n * chrome and nests the content view. The inner tool travels via the\n * `embed-tool-id` attribute; tool picks come back as\n * `patchwork:embed-tool-changed` events and are persisted by rewriting the\n * marker (which recreates the widget).\n */\nclass EmbedWidget extends WidgetType {\n readonly docId: DocumentId;\n // `null` = no pinned tool; the embed frame uses the datatype's fallback.\n readonly toolId: string | null;\n readonly embedText: string;\n\n constructor(docId: DocumentId, toolId: string | null, embedText: string) {\n super();\n this.docId = docId;\n this.toolId = toolId;\n this.embedText = embedText;\n }\n\n eq(other: EmbedWidget) {\n return other.docId === this.docId && other.toolId === this.toolId;\n }\n\n toDOM(view: EditorView) {\n const container = document.createElement(\"div\");\n container.className = \"cm-embed\";\n\n // An image with no pinned tool is just drawn, with no frame round it. We\n // can't tell until the doc loads, so hold off mounting the frame until then.\n if (this.toolId) {\n this.mountFrame(view, container);\n } else {\n const cleanup = mountImageOrFrame(this.docId, container, () =>\n this.mountFrame(view, container)\n );\n cleanups.set(container, cleanup);\n }\n return container;\n }\n\n destroy(dom: HTMLElement) {\n cleanups.get(dom)?.();\n cleanups.delete(dom);\n }\n\n private mountFrame(view: EditorView, container: HTMLElement): void {\n const patchworkView = document.createElement(\"patchwork-view\");\n // No heads in the url: the embed follows whatever version the host\n // resolves \u2014 the active checkpoint when one is checked out, live otherwise.\n patchworkView.setAttribute(\"doc-url\", `automerge:${this.docId}`);\n patchworkView.setAttribute(\"tool-id\", \"embed\");\n if (this.toolId) patchworkView.setAttribute(\"embed-tool-id\", this.toolId);\n // Explicit inline height: without it the element collapses to 0px (the\n // stylesheet rule isn't reliably applied here).\n patchworkView.style.display = \"block\";\n patchworkView.style.height = \"500px\";\n patchworkView.style.width = \"100%\";\n\n // Persist tool picks into the marker text. Stop propagation so nested\n // embeds don't also change the outer embed's tool.\n container.addEventListener(\"patchwork:embed-tool-changed\", (e) => {\n e.stopPropagation();\n const toolId = (e as CustomEvent<{ toolId?: string }>).detail?.toolId;\n if (toolId) this.setTool(view, container, toolId);\n });\n\n container.appendChild(patchworkView);\n }\n\n private setTool(\n view: EditorView,\n container: HTMLElement,\n toolId: string\n ): void {\n const from = view.posAtDOM(container);\n const to = from + this.embedText.length;\n // Only rewrite if the marker is still exactly where this widget maps to.\n if (view.state.doc.sliceString(from, to) !== this.embedText) return;\n view.dispatch({\n changes: { from, to, insert: embedSyntax({ docId: this.docId, toolId }) },\n });\n }\n\n ignoreEvent() {\n // Atomic embed: the editor ignores all events; the frame handles them.\n return true;\n }\n}\n\nconst cleanups = new WeakMap<HTMLElement, () => void>();\n\nconst IMAGE_EXTENSIONS = [\"svg\", \"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"bmp\"];\n\ntype FileDoc = {\n \"@patchwork\"?: { type?: string };\n content?: unknown;\n mimeType?: string;\n extension?: string;\n name?: string;\n};\n\nfunction isImageDoc(doc: FileDoc | undefined): boolean {\n if (doc?.[\"@patchwork\"]?.type !== \"file\") return false;\n if (!(doc.content instanceof Uint8Array)) return false;\n return (\n !!doc.mimeType?.startsWith(\"image/\") ||\n IMAGE_EXTENSIONS.includes(doc.extension?.toLowerCase() ?? \"\")\n );\n}\n\n/**\n * What each embedded doc turned out to be, kept for the life of the page.\n *\n * CodeMirror throws a line's DOM away when it scrolls out of view and calls\n * `toDOM` again when it comes back. Waiting on `repo.find` each time would\n * remount the embed at 0px and grow it a moment later, jumping the text\n * (scrolling up, the growth lands above what you're reading). With this the\n * remount is drawn at once, at its last size. The object urls stay alive with\n * the entry, so a remounted `<img>` needs no re-decode of the bytes into a\n * blob either.\n */\ntype Seen =\n | { kind: \"frame\" }\n | {\n kind: \"image\";\n content: Uint8Array;\n url: string;\n name: string;\n width?: number;\n height?: number;\n };\nconst seen = new Map<DocumentId, Seen>();\n\ntype ImageSeen = Extract<Seen, { kind: \"image\" }>;\n\n/** The cached image for this doc, made fresh if its bytes changed. */\nfunction imageFor(docId: DocumentId, doc: FileDoc): ImageSeen {\n const prev = seen.get(docId);\n if (prev?.kind === \"image\" && prev.content === doc.content) return prev;\n if (prev?.kind === \"image\") URL.revokeObjectURL(prev.url);\n const content = doc.content as Uint8Array;\n const next: ImageSeen = {\n kind: \"image\",\n content,\n url: URL.createObjectURL(\n new Blob([content as BlobPart], { type: doc.mimeType || \"\" })\n ),\n name: doc.name ?? \"\",\n };\n seen.set(docId, next);\n return next;\n}\n\n/**\n * Draw an image doc as a bare `<img>` that follows the doc's content; anything\n * else (or no `window.repo`) gets the normal embed frame. A doc seen before is\n * drawn straight away from `seen`. Returns a cleanup for when the widget goes.\n *\n * Reads through the realm-local `window.repo`, so an image changed only in a\n * draft shows its main-branch bytes here.\n */\nfunction mountImageOrFrame(\n docId: DocumentId,\n container: HTMLElement,\n mountFrame: () => void\n): () => void {\n const known = seen.get(docId);\n if (known?.kind === \"frame\") {\n mountFrame();\n return () => {};\n }\n\n const repo = (window as unknown as { repo?: any }).repo;\n let disposed = false;\n let handle: any;\n let img: HTMLImageElement | undefined;\n\n const show = (image: ImageSeen) => {\n if (!img) {\n container.classList.add(\"cm-embed-image\");\n img = document.createElement(\"img\");\n // Width/height attributes give the box its aspect ratio before the\n // image decodes (the theme keeps `height: auto`).\n img.addEventListener(\"load\", () => {\n const current = seen.get(docId);\n if (current?.kind !== \"image\" || img!.src !== current.url) return;\n current.width = img!.naturalWidth;\n current.height = img!.naturalHeight;\n img!.width = current.width;\n img!.height = current.height;\n });\n container.appendChild(img);\n }\n if (img.getAttribute(\"src\") === image.url) return;\n if (image.width && image.height) {\n img.width = image.width;\n img.height = image.height;\n } else {\n img.removeAttribute(\"width\");\n img.removeAttribute(\"height\");\n }\n img.alt = image.name;\n img.src = image.url;\n };\n\n const render = () => {\n const doc = handle.doc() as FileDoc | undefined;\n if (isImageDoc(doc)) show(imageFor(docId, doc!));\n };\n\n if (known?.kind === \"image\") show(known);\n\n if (!repo?.find) {\n if (!img) mountFrame();\n return () => {};\n }\n\n Promise.resolve(repo.find(`automerge:${docId}`)).then(\n (h) => {\n if (disposed) return;\n handle = h;\n const doc = handle.doc();\n if (!isImageDoc(doc)) {\n if (doc) seen.set(docId, { kind: \"frame\" });\n img?.remove();\n container.classList.remove(\"cm-embed-image\");\n mountFrame();\n return;\n }\n render();\n handle.on(\"change\", render);\n },\n () => {\n if (!disposed && !img) mountFrame();\n }\n );\n\n return () => {\n disposed = true;\n handle?.off?.(\"change\", render);\n };\n}\n\n// Marker syntax: [patchwork:docId] or [patchwork:docId/toolId] (ids can't\n// contain `/` or `]`). We scan plain text instead of the markdown syntax tree\n// on purpose: `@codemirror/language` isn't a shared singleton across\n// patchwork's separately-bundled extensions, so `syntaxTree(state)` here would\n// read a different `Language` facet and always come back empty.\nconst EMBED_PATTERN = /\\[patchwork:([^/\\]]+)(?:\\/([^\\]]+))?\\]/g;\n\nfunction getEmbedLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n\n // Visible ranges are line-aligned and markers never span lines, so a marker\n // is always fully inside or fully outside a range.\n for (const { from, to } of view.visibleRanges) {\n const text = state.doc.sliceString(from, to);\n EMBED_PATTERN.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = EMBED_PATTERN.exec(text)) !== null) {\n const matchFrom = from + m.index;\n const matchTo = matchFrom + m[0].length;\n const [matchText, docId, toolId] = m;\n\n if (!isValidDocumentId(docId)) continue;\n\n const embed = Decoration.replace({\n widget: new EmbedWidget(docId as DocumentId, toolId ?? null, matchText),\n });\n widgets.push(embed.range(matchFrom, matchTo));\n }\n }\n\n // `true` lets CodeMirror sort the ranges defensively (matches are already in\n // document order, but this is cheap insurance).\n return Decoration.set(widgets, true);\n}\n\n// MIME types we accept document drags from. Mirrors the sideboard's convention.\nconst PATCHWORK_DND = \"text/x-patchwork-dnd\";\nconst PATCHWORK_URLS = \"text/x-patchwork-urls\";\n\ntype DocRef = { docId: DocumentId; toolId: string | null };\n\n/** Turn an automerge url or a patchwork web link into a DocumentId, or null. */\nfunction urlToDocId(raw: string): DocumentId | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n // Plain automerge url (optionally carrying heads/query/subpath).\n const am = trimmed.match(/automerge:([a-zA-Z0-9]+)/);\n if (am && isValidDocumentId(am[1])) return am[1] as DocumentId;\n // Patchwork web link: #doc=<documentId> (also &doc= / ?doc=).\n const web = trimmed.match(/[#&?]doc=([a-zA-Z0-9]+)/);\n if (web && isValidDocumentId(web[1])) return web[1] as DocumentId;\n return null;\n}\n\n/**\n * Read dragged documents out of a drop event, in order of format preference.\n * Only an *explicit* `toolId` on a structured item pins the tool; otherwise we\n * embed tool-less and let the view fall back to the datatype's default tool.\n */\nfunction extractDocRefs(dt: DataTransfer): DocRef[] {\n const refs: DocRef[] = [];\n const seen = new Set<string>();\n const push = (docId: DocumentId | null, toolId: string | null) => {\n if (!docId || seen.has(docId)) return;\n seen.add(docId);\n refs.push({ docId, toolId });\n };\n\n const dnd = dt.getData(PATCHWORK_DND);\n if (dnd) {\n try {\n const parsed = JSON.parse(dnd) as {\n items?: Array<{ url?: string; toolId?: string }>;\n };\n for (const item of parsed?.items ?? []) {\n if (item?.url) push(urlToDocId(item.url), item.toolId ?? null);\n }\n } catch {\n // fall through to the other formats\n }\n }\n if (refs.length > 0) return refs;\n\n const urls = dt.getData(PATCHWORK_URLS);\n if (urls) {\n try {\n const parsed: unknown = JSON.parse(urls);\n if (Array.isArray(parsed)) {\n for (const u of parsed) push(urlToDocId(String(u)), null);\n }\n } catch {\n // fall through\n }\n }\n if (refs.length > 0) return refs;\n\n const text = dt.getData(\"text/uri-list\") || dt.getData(\"text/plain\");\n if (text) {\n for (const line of text.split(/\\r?\\n/)) {\n if (line.startsWith(\"#\")) continue; // uri-list comments\n push(urlToDocId(line), null);\n }\n }\n return refs;\n}\n\nfunction embedSyntax({ docId, toolId }: DocRef): string {\n return toolId ? `[patchwork:${docId}/${toolId}]` : `[patchwork:${docId}]`;\n}\n\n/**\n * Import each OS file (dropped from the desktop or pasted) as a Patchwork\n * `file` document, returning tool-less refs to embed. Uses the realm-local\n * `window.repo` (the documented global) \u2014 fine for creating brand-new docs,\n * which aren't subject to draft remapping.\n */\nasync function importFileRefs(files: Iterable<File>): Promise<DocRef[]> {\n const repo = (window as unknown as { repo?: any }).repo;\n if (!repo) {\n console.warn(\"[codemirror-embed] window.repo unavailable; ignoring files\");\n return [];\n }\n const refs: DocRef[] = [];\n for (const file of files) {\n try {\n const mimeType = file.type || \"application/octet-stream\";\n const isText =\n mimeType.startsWith(\"text/\") || mimeType === \"application/json\";\n const content = isText\n ? await file.text()\n : new Uint8Array(await file.arrayBuffer());\n const parts = file.name.split(\".\");\n const extension = parts.length > 1 ? parts.pop()! : \"\";\n const handle = repo.create();\n handle.change((d: any) => {\n d[\"@patchwork\"] = { type: \"file\" };\n d.content = content;\n d.mimeType = mimeType;\n d.extension = extension;\n d.name = file.name;\n });\n const { documentId } = parseAutomergeUrl(handle.url as AutomergeUrl);\n if (isValidDocumentId(documentId)) {\n refs.push({ docId: documentId as DocumentId, toolId: null });\n }\n } catch (err) {\n console.warn(\n \"[codemirror-embed] failed to import file\",\n file.name,\n err\n );\n }\n }\n return refs;\n}\n\n/** Insert embeds over `from..to` (a drop point, or a pasted-over selection). */\nfunction insertRefs(\n view: EditorView,\n from: number,\n refs: DocRef[],\n to = from\n): void {\n if (refs.length === 0) return;\n // Files import asynchronously, so the doc may have shrunk in the meantime.\n const len = view.state.doc.length;\n from = Math.min(from, len);\n to = Math.min(to, len);\n let text = refs.map(embedSyntax).join(\"\\n\\n\");\n // On a non-empty line, add just enough breaks to put the embed on its own line.\n const { doc } = view.state;\n const start = doc.lineAt(from);\n const end = doc.lineAt(to);\n if (/\\S/.test(start.text) && from > start.from) text = \"\\n\" + text;\n if (/\\S/.test(end.text) && to < end.to) text = text + \"\\n\";\n view.dispatch({\n changes: { from, to, insert: text },\n selection: { anchor: from + text.length },\n });\n}\n\n/**\n * Drop handler that accepts:\n * - documents dragged from the sidebar / canvases / other tools\n * (`text/x-patchwork-dnd`, `text/x-patchwork-urls`, `text/uri-list`, and\n * `text/plain` patchwork links), and\n * - files dragged in from the operating system (imported as `file` docs).\n * Inserts `[patchwork:docId]` (or `[patchwork:docId/toolId]`) at the drop point.\n */\nfunction embedDropHandlers() {\n // Only claim the *dragover* for unambiguous patchwork/OS drags \u2014 plain\n // text/uri-list can be an ordinary in-editor text drag, which we must not\n // swallow. (`Files` covers OS drags, whose `dt.files` is empty until drop.)\n const wantsDragover = (dt: DataTransfer | null): boolean =>\n !!dt &&\n (dt.types.includes(\"Files\") ||\n dt.types.includes(PATCHWORK_DND) ||\n dt.types.includes(PATCHWORK_URLS));\n\n return EditorView.domEventHandlers({\n dragover(event) {\n const dt = event.dataTransfer;\n if (!wantsDragover(dt)) return false;\n event.preventDefault();\n // Doc drags insert a *reference* to the same automerge doc, so show the\n // link cursor; OS file drags genuinely copy content into new docs.\n if (dt) dt.dropEffect = dt.types.includes(\"Files\") ? \"copy\" : \"link\";\n return true;\n },\n drop(event, view) {\n const dt = event.dataTransfer;\n if (!dt) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n\n // OS files import asynchronously; insert once the docs exist.\n if (dt.files && dt.files.length > 0) {\n event.preventDefault();\n void importFileRefs(Array.from(dt.files)).then((refs) =>\n insertRefs(view, pos, refs)\n );\n return true;\n }\n\n // Otherwise only handle the drop if it actually resolves to patchwork\n // docs (dnd/urls always do; uri-list/plain only for patchwork links).\n // If not, let CodeMirror handle it as a normal text drop.\n const refs = extractDocRefs(dt);\n if (refs.length === 0) return false;\n event.preventDefault();\n insertRefs(view, pos, refs);\n return true;\n },\n paste(event, view) {\n // Image pastes (screenshots, \"Copy Image\") become embedded `file` docs.\n // A paste that also carries text is left to CodeMirror: rich text from\n // office apps attaches a picture of itself, and a Finder copy carries\n // the file's icon alongside its name.\n const dt = event.clipboardData;\n if (!dt || dt.getData(\"text/plain\")) return false;\n const images = Array.from(dt.files ?? []).filter((f) =>\n f.type.startsWith(\"image/\")\n );\n if (images.length === 0) return false;\n event.preventDefault();\n const { from, to } = view.state.selection.main;\n void importFileRefs(images).then((refs) =>\n insertRefs(view, from, refs, to)\n );\n return true;\n },\n });\n}\n\nconst embedPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getEmbedLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Viewport too, so newly-visible markers get decorated.\n if (update.docChanged || update.viewportChanged) {\n this.decorations = getEmbedLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n // Atomic: the cursor skips embeds and delete removes the whole marker.\n provide: (plugin) =>\n EditorView.atomicRanges.of(\n (view) => view.plugin(plugin)?.decorations ?? Decoration.none\n ),\n }\n);\n\nexport function markdownEmbed() {\n // `dropCursor` observes events, so it coexists with our dragover handler.\n return [embedPlugin, embedTheme, embedDropHandlers(), dropCursor()];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\n// Outer container only; all embed chrome lives in the shared \"embed\" tool.\nexport const embedTheme = EditorView.baseTheme({\n \".cm-embed\": {\n display: \"block\",\n border: \"var(--studio-border-width, 1px) var(--studio-border-style, solid)\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n },\n \"&light .cm-embed\": {\n borderColor: \"#ddd\",\n },\n \"&dark .cm-embed\": {\n borderColor: \"#333\",\n },\n // Images are just images: no border, natural size, never wider than the text.\n \"&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image\": {\n border: \"none\",\n borderRadius: \"0\",\n },\n \".cm-embed-image > img\": {\n display: \"block\",\n maxWidth: \"100%\",\n height: \"auto\",\n },\n \".cm-embed > patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["EditorView", "seen", "EditorView", "refs"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/extensions/links.ts
|
|
2
2
|
import {
|
|
3
|
+
EditorView as EditorView2,
|
|
3
4
|
Decoration,
|
|
4
5
|
WidgetType,
|
|
5
6
|
ViewPlugin
|
|
@@ -18,10 +19,28 @@ var linkTheme = EditorView.baseTheme({
|
|
|
18
19
|
"&:hover": {
|
|
19
20
|
color: "inherit"
|
|
20
21
|
}
|
|
22
|
+
},
|
|
23
|
+
"&.cm-mod-held .cm-md-link, &.cm-mod-held .cm-md-link *": {
|
|
24
|
+
textDecoration: "underline",
|
|
25
|
+
cursor: "var(--studio-cursor-pointer, pointer)"
|
|
21
26
|
}
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
// src/extensions/links.ts
|
|
30
|
+
function openUrl(url, from) {
|
|
31
|
+
if (isValidAutomergeUrl(url)) {
|
|
32
|
+
from.dispatchEvent(
|
|
33
|
+
new CustomEvent("patchwork:open-document", {
|
|
34
|
+
detail: { url },
|
|
35
|
+
bubbles: true,
|
|
36
|
+
composed: true
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
const href = /^[a-z][a-z0-9+.-]*:/i.test(url) ? url : `https://${url}`;
|
|
41
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
25
44
|
var LinkWidget = class extends WidgetType {
|
|
26
45
|
url;
|
|
27
46
|
text;
|
|
@@ -41,17 +60,7 @@ var LinkWidget = class extends WidgetType {
|
|
|
41
60
|
link.title = this.url;
|
|
42
61
|
link.onclick = (e) => {
|
|
43
62
|
e.preventDefault();
|
|
44
|
-
|
|
45
|
-
link.dispatchEvent(
|
|
46
|
-
new CustomEvent("patchwork:open-document", {
|
|
47
|
-
detail: { url: this.url },
|
|
48
|
-
bubbles: true,
|
|
49
|
-
composed: true
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
} else {
|
|
53
|
-
window.open(this.url, "_blank noopener noreferrer");
|
|
54
|
-
}
|
|
63
|
+
openUrl(this.url, link);
|
|
55
64
|
};
|
|
56
65
|
return link;
|
|
57
66
|
}
|
|
@@ -106,10 +115,94 @@ var linkPlugin = ViewPlugin.fromClass(
|
|
|
106
115
|
decorations: (v) => v.decorations
|
|
107
116
|
}
|
|
108
117
|
);
|
|
118
|
+
function urlAt(state, pos) {
|
|
119
|
+
for (const side of [1, -1]) {
|
|
120
|
+
for (let node = syntaxTree(state).resolveInner(pos, side); node; node = node.parent) {
|
|
121
|
+
if (node.name === "URL") return state.doc.sliceString(node.from, node.to);
|
|
122
|
+
if (node.name === "Link" || node.name === "Autolink") {
|
|
123
|
+
const url = node.getChild("URL");
|
|
124
|
+
return url ? state.doc.sliceString(url.from, url.to) : null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);
|
|
131
|
+
var modHeld = (e) => isMac ? e.metaKey : e.ctrlKey;
|
|
132
|
+
var linkMark = Decoration.mark({ class: "cm-md-link" });
|
|
133
|
+
var linkMarks = ViewPlugin.fromClass(
|
|
134
|
+
class {
|
|
135
|
+
decorations;
|
|
136
|
+
constructor(view) {
|
|
137
|
+
this.decorations = this.build(view);
|
|
138
|
+
}
|
|
139
|
+
update(update) {
|
|
140
|
+
if (update.docChanged || update.viewportChanged || syntaxTree(update.startState) !== syntaxTree(update.state)) {
|
|
141
|
+
this.decorations = this.build(update.view);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
build(view) {
|
|
145
|
+
const marks = [];
|
|
146
|
+
for (const { from, to } of view.visibleRanges) {
|
|
147
|
+
syntaxTree(view.state).iterate({
|
|
148
|
+
from,
|
|
149
|
+
to,
|
|
150
|
+
enter: (node) => {
|
|
151
|
+
const name = node.name;
|
|
152
|
+
if (name === "Link" || name === "Autolink") {
|
|
153
|
+
if (node.node.getChild("URL"))
|
|
154
|
+
marks.push(linkMark.range(node.from, node.to));
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (name === "URL") marks.push(linkMark.range(node.from, node.to));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return Decoration.set(marks, true);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ decorations: (v) => v.decorations }
|
|
165
|
+
);
|
|
166
|
+
var modClick = [
|
|
167
|
+
EditorView2.domEventHandlers({
|
|
168
|
+
mousedown(event, view) {
|
|
169
|
+
if (event.button !== 0 || !modHeld(event)) return false;
|
|
170
|
+
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
|
|
171
|
+
if (pos == null) return false;
|
|
172
|
+
const url = urlAt(view.state, pos);
|
|
173
|
+
if (!url) return false;
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
openUrl(url, view.dom);
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
keydown(event, view) {
|
|
179
|
+
view.dom.classList.toggle("cm-mod-held", modHeld(event));
|
|
180
|
+
return false;
|
|
181
|
+
},
|
|
182
|
+
keyup(event, view) {
|
|
183
|
+
view.dom.classList.toggle("cm-mod-held", modHeld(event));
|
|
184
|
+
return false;
|
|
185
|
+
},
|
|
186
|
+
mousemove(event, view) {
|
|
187
|
+
view.dom.classList.toggle("cm-mod-held", modHeld(event));
|
|
188
|
+
return false;
|
|
189
|
+
},
|
|
190
|
+
mouseleave(_event, view) {
|
|
191
|
+
view.dom.classList.remove("cm-mod-held");
|
|
192
|
+
return false;
|
|
193
|
+
},
|
|
194
|
+
blur(_event, view) {
|
|
195
|
+
view.dom.classList.remove("cm-mod-held");
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
];
|
|
109
200
|
function markdownLinks() {
|
|
110
|
-
return [linkPlugin, linkTheme];
|
|
201
|
+
return [linkPlugin, linkMarks, modClick, linkTheme];
|
|
111
202
|
}
|
|
112
203
|
export {
|
|
113
|
-
markdownLinks
|
|
204
|
+
markdownLinks,
|
|
205
|
+
openUrl,
|
|
206
|
+
urlAt
|
|
114
207
|
};
|
|
115
208
|
//# sourceMappingURL=links.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/extensions/links.ts", "../../src/themes/links.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 { syntaxTree } from \"@codemirror/language\";\nimport { isValidAutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { linkTheme } from \"../themes/links.ts\";\n\n/**\n * Widget to render a clickable link in the editor\n */\nclass LinkWidget extends WidgetType {\n readonly url: string;\n readonly text: string;\n\n constructor(url: string, text: string) {\n super();\n this.url = url;\n this.text = text;\n }\n\n eq(other: LinkWidget) {\n return other.url === this.url && other.text === this.text;\n }\n\n toDOM() {\n const link = document.createElement(\"a\");\n link.href = this.url;\n link.textContent = this.text;\n link.className = \"cm-link\";\n link.title = this.url;\n\n link.onclick = (e) => {\n e.preventDefault();\n
|
|
5
|
-
"mappings": ";AAAA;AAAA,
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport { type EditorState, Range } from \"@codemirror/state\";\nimport { syntaxTree } from \"@codemirror/language\";\nimport { isValidAutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { linkTheme } from \"../themes/links.ts\";\n\n/**\n * Open a link from the editor. Automerge urls open inside Patchwork rather\n * than as a browser URL (automerge: isn't navigable); bubbles/composed so the\n * event escapes the editor and any shadow root up to whoever drives selection.\n */\nexport function openUrl(url: string, from: Element) {\n if (isValidAutomergeUrl(url)) {\n from.dispatchEvent(\n new CustomEvent(\"patchwork:open-document\", {\n detail: { url },\n bubbles: true,\n composed: true,\n }),\n );\n } else {\n // GFM autolinks `www.example.com` with no scheme.\n const href = /^[a-z][a-z0-9+.-]*:/i.test(url) ? url : `https://${url}`;\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n }\n}\n\n/**\n * Widget to render a clickable link in the editor\n */\nclass LinkWidget extends WidgetType {\n readonly url: string;\n readonly text: string;\n\n constructor(url: string, text: string) {\n super();\n this.url = url;\n this.text = text;\n }\n\n eq(other: LinkWidget) {\n return other.url === this.url && other.text === this.text;\n }\n\n toDOM() {\n const link = document.createElement(\"a\");\n link.href = this.url;\n link.textContent = this.text;\n link.className = \"cm-link\";\n link.title = this.url;\n\n link.onclick = (e) => {\n e.preventDefault();\n openUrl(this.url, link);\n };\n\n return link;\n }\n\n ignoreEvent(e: Event) {\n // Let click events through to our widget\n return e.type !== \"click\";\n }\n}\n\nfunction getLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n const selection = state.selection.main;\n\n for (let { from, to } of view.visibleRanges) {\n syntaxTree(state).iterate({\n from,\n to,\n enter: (node) => {\n // Match markdown link syntax\n if (node.name === \"Link\") {\n const linkFrom = node.from;\n const linkTo = node.to;\n\n // Check if cursor is inside this link\n const cursorInLink =\n selection.from >= linkFrom && selection.from <= linkTo;\n // Check if the link is inside the selection\n const selectionSpansLink =\n selection.from < linkFrom && selection.to > linkTo;\n\n // Replace if cursor is outside the link and there's no selection across the link\n if (!cursorInLink && !selectionSpansLink) {\n const linkText = state.doc.sliceString(linkFrom, linkTo);\n const match = linkText.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n\n if (match) {\n const [, text, url] = match;\n\n // Replace the entire [text](url) with just the link widget\n const deco = Decoration.replace({\n widget: new LinkWidget(url, text),\n });\n\n widgets.push(deco.range(linkFrom, linkTo));\n }\n }\n }\n },\n });\n }\n\n return Decoration.set(widgets);\n}\n\nconst linkPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Recompute when doc changes, selection moves, or viewport changes\n if (\n update.docChanged ||\n update.selectionSet ||\n update.viewportChanged ||\n syntaxTree(update.startState) !== syntaxTree(update.state)\n ) {\n this.decorations = getLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n }\n);\n\ntype SyntaxNode = ReturnType<ReturnType<typeof syntaxTree>[\"resolveInner\"]>;\n\n/**\n * The url of the link at `pos`, if any: an inline `[text](url)`, an autolink\n * `<url>`, a GFM bare url, or the url in a `[ref]: url` definition.\n * Reference-style `[text][ref]` links have no url of their own and are skipped.\n */\nexport function urlAt(state: EditorState, pos: number): string | null {\n for (const side of [1, -1] as const) {\n for (\n let node: SyntaxNode | null = syntaxTree(state).resolveInner(pos, side);\n node;\n node = node.parent\n ) {\n if (node.name === \"URL\") return state.doc.sliceString(node.from, node.to);\n if (node.name === \"Link\" || node.name === \"Autolink\") {\n const url = node.getChild(\"URL\");\n return url ? state.doc.sliceString(url.from, url.to) : null;\n }\n }\n }\n return null;\n}\n\nconst isMac =\n typeof navigator !== \"undefined\" &&\n /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);\nconst modHeld = (e: KeyboardEvent | MouseEvent) =>\n isMac ? e.metaKey : e.ctrlKey;\n\n/** Marks link text so it can look clickable while Mod is held. */\nconst linkMark = Decoration.mark({ class: \"cm-md-link\" });\n\nconst linkMarks = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n constructor(view: EditorView) {\n this.decorations = this.build(view);\n }\n update(update: ViewUpdate) {\n if (\n update.docChanged ||\n update.viewportChanged ||\n syntaxTree(update.startState) !== syntaxTree(update.state)\n ) {\n this.decorations = this.build(update.view);\n }\n }\n build(view: EditorView) {\n const marks: Range<Decoration>[] = [];\n for (const { from, to } of view.visibleRanges) {\n syntaxTree(view.state).iterate({\n from,\n to,\n enter: (node) => {\n const name = node.name;\n if (name === \"Link\" || name === \"Autolink\") {\n if (node.node.getChild(\"URL\"))\n marks.push(linkMark.range(node.from, node.to));\n return false;\n }\n if (name === \"URL\") marks.push(linkMark.range(node.from, node.to));\n },\n });\n }\n return Decoration.set(marks, true);\n }\n },\n { decorations: (v) => v.decorations },\n);\n\n/**\n * Cmd-click (Ctrl off a Mac) opens the link under the pointer, even while the\n * cursor is inside it and it's showing as raw markdown. Holding the key puts\n * `cm-mod-held` on the editor so the theme can show links as clickable.\n */\nconst modClick = [\n EditorView.domEventHandlers({\n mousedown(event, view) {\n if (event.button !== 0 || !modHeld(event)) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n const url = urlAt(view.state, pos);\n if (!url) return false;\n event.preventDefault();\n openUrl(url, view.dom);\n return true;\n },\n keydown(event, view) {\n view.dom.classList.toggle(\"cm-mod-held\", modHeld(event));\n return false;\n },\n keyup(event, view) {\n view.dom.classList.toggle(\"cm-mod-held\", modHeld(event));\n return false;\n },\n mousemove(event, view) {\n view.dom.classList.toggle(\"cm-mod-held\", modHeld(event));\n return false;\n },\n mouseleave(_event, view) {\n view.dom.classList.remove(\"cm-mod-held\");\n return false;\n },\n blur(_event, view) {\n view.dom.classList.remove(\"cm-mod-held\");\n return false;\n },\n }),\n];\n\nexport function markdownLinks() {\n return [linkPlugin, linkMarks, modClick, linkTheme];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\nexport const linkTheme = EditorView.baseTheme({\n \".cm-link\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n fontFamily: '\"Merriweather Sans\", sans-serif',\n color: \"var(--syntax-link, #0066cc)\",\n \"&:hover\": {\n color: \"inherit\",\n },\n },\n \"&.cm-mod-held .cm-md-link, &.cm-mod-held .cm-md-link *\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["EditorView", "EditorView"]
|
|
7
7
|
}
|
|
@@ -2185,6 +2185,7 @@ function toggle(view, from) {
|
|
|
2185
2185
|
userEvent: "input.toggle-task"
|
|
2186
2186
|
});
|
|
2187
2187
|
}
|
|
2188
|
+
var doneMark = Decoration2.mark({ class: "cm-task-done" });
|
|
2188
2189
|
function build(view) {
|
|
2189
2190
|
const builder = new RangeSetBuilder2();
|
|
2190
2191
|
const { doc } = view.state;
|
|
@@ -2200,6 +2201,10 @@ function build(view) {
|
|
|
2200
2201
|
node.to,
|
|
2201
2202
|
Decoration2.replace({ widget: new TaskWidget(checked) })
|
|
2202
2203
|
);
|
|
2204
|
+
const task = node.node.parent;
|
|
2205
|
+
if (checked && task?.name === "Task" && task.to > node.to) {
|
|
2206
|
+
builder.add(node.to, task.to, doneMark);
|
|
2207
|
+
}
|
|
2203
2208
|
}
|
|
2204
2209
|
});
|
|
2205
2210
|
}
|
|
@@ -2339,6 +2344,9 @@ var markdownStyles = (style) => ({
|
|
|
2339
2344
|
".cm-task:disabled": {
|
|
2340
2345
|
cursor: "default"
|
|
2341
2346
|
},
|
|
2347
|
+
".cm-task-done": {
|
|
2348
|
+
opacity: 0.6
|
|
2349
|
+
},
|
|
2342
2350
|
".cm-activeLine": {
|
|
2343
2351
|
backgroundColor: "inherit"
|
|
2344
2352
|
},
|