@inkandswitch/patchwork-pkg-base 0.0.129 → 0.0.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- automerge:2iDEUDFih3pvTvULKfnP6GvTHqMP
1
+ automerge:J6CANwphTqDXiMtqmCb9ThXErcK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.129",
3
+ "version": "0.0.130",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -52,6 +52,7 @@ function createCommentForRange(handle, path, from, to, contactUrl) {
52
52
  var openPopover = StateEffect.define();
53
53
  var closePopover = StateEffect.define();
54
54
  var dismissButton = StateEffect.define();
55
+ var setPointerDown = StateEffect.define();
55
56
  var POPOVER_CLASS = "cm-comment-popover";
56
57
  var buildCommentButton = (from, to, head, view, options) => {
57
58
  const button = document.createElement("button");
@@ -135,11 +136,13 @@ var commentUIField = (options, ui) => StateField.define({
135
136
  create: (state) => ({
136
137
  popover: null,
137
138
  buttonDismissed: false,
139
+ pointerDown: false,
138
140
  tooltip: buttonTooltip(state, options)
139
141
  }),
140
142
  update(value, tr) {
141
143
  let popover = value.popover;
142
144
  let buttonDismissed = value.buttonDismissed;
145
+ let pointerDown = value.pointerDown;
143
146
  let popoverChanged = false;
144
147
  const readOnlyChanged = tr.startState.readOnly !== tr.state.readOnly;
145
148
  if (readOnlyChanged && tr.state.readOnly && popover) {
@@ -160,32 +163,48 @@ var commentUIField = (options, ui) => StateField.define({
160
163
  buttonDismissed = true;
161
164
  } else if (effect.is(dismissButton)) {
162
165
  buttonDismissed = true;
166
+ } else if (effect.is(setPointerDown)) {
167
+ pointerDown = effect.value;
163
168
  }
164
169
  }
165
170
  if (tr.selection) buttonDismissed = false;
166
171
  if (popover) {
167
172
  if (!popoverChanged) {
168
- return buttonDismissed === value.buttonDismissed ? value : { ...value, buttonDismissed };
173
+ return buttonDismissed === value.buttonDismissed && pointerDown === value.pointerDown ? value : { ...value, buttonDismissed, pointerDown };
169
174
  }
170
175
  return {
171
176
  popover,
172
177
  buttonDismissed,
178
+ pointerDown,
173
179
  tooltip: popoverTooltip(popover, options, ui)
174
180
  };
175
181
  }
176
- if (!popoverChanged && !tr.docChanged && !tr.selection && !readOnlyChanged && buttonDismissed === value.buttonDismissed) {
182
+ if (!popoverChanged && !tr.docChanged && !tr.selection && !readOnlyChanged && buttonDismissed === value.buttonDismissed && pointerDown === value.pointerDown) {
177
183
  return value;
178
184
  }
179
185
  return {
180
186
  popover: null,
181
187
  buttonDismissed,
182
- tooltip: buttonDismissed ? null : buttonTooltip(tr.state, options)
188
+ pointerDown,
189
+ tooltip: buttonDismissed || pointerDown ? null : buttonTooltip(tr.state, options)
183
190
  };
184
191
  },
185
192
  provide: (field) => showTooltip.from(field, (value) => value.tooltip)
186
193
  });
194
+ var trackPointer = (view) => {
195
+ view.dispatch({ effects: setPointerDown.of(true) });
196
+ const up = () => {
197
+ view.win.removeEventListener("mouseup", up, true);
198
+ view.win.removeEventListener("blur", up);
199
+ if (view.dom.isConnected)
200
+ view.dispatch({ effects: setPointerDown.of(false) });
201
+ };
202
+ view.win.addEventListener("mouseup", up, true);
203
+ view.win.addEventListener("blur", up);
204
+ };
187
205
  var commentInteractions = (options, ui, field) => EditorView.domEventHandlers({
188
206
  mousedown(event, view) {
207
+ if (event.button === 0) trackPointer(view);
189
208
  if (event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
190
209
  return false;
191
210
  }
@@ -257,7 +276,13 @@ var commentUITheme = EditorView.baseTheme({
257
276
  cursor: "var(--studio-cursor-pointer, pointer)",
258
277
  userSelect: "none",
259
278
  whiteSpace: "nowrap",
260
- transition: "background-color max(1ms, calc(0.15s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))"
279
+ transition: "background-color max(1ms, calc(0.15s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))",
280
+ // fades in once the selection's made (it waits for the mouse to come up)
281
+ animation: "cm-comment-button-in max(1ms, calc(0.16s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1))) ease-out both"
282
+ },
283
+ "@keyframes cm-comment-button-in": {
284
+ from: { opacity: "0", transform: "translateY(2px)" },
285
+ to: { opacity: "1", transform: "none" }
261
286
  },
262
287
  ".cm-comment-button:hover": {
263
288
  background: "var(--cm-comment-button-bg-hover)"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/tool.tsx", "../src/lib/extensions/commentUI.ts", "../src/lib/extensions/comments.ts", "../node_modules/.pnpm/@inkandswitch+patchwork-providers-solid@0.2.8_@automerge+automerge-repo@2.6.0-subductio_64b21ee2f70813c59f45ca0bcbd2a52c/node_modules/@inkandswitch/patchwork-providers-solid/dist/index.js"],
4
- "sourcesContent": ["import { template as _$template } from \"solid-js/web\";\nimport { insert as _$insert } from \"solid-js/web\";\nimport { createComponent as _$createComponent } from \"solid-js/web\";\nvar _tmpl$ = /*#__PURE__*/_$template(`<div style=\"width:100%;height:100%;overflow:hidden;position:relative;background:var(--studio-fill, white)\"><div style=display:flex;height:100%><div style=position:relative;flex:1;height:100%>`);\nimport { CodeMirror } from \"./lib/codemirror.tsx\";\nimport { render } from \"solid-js/web\";\nimport { RangeSet } from \"@codemirror/state\";\nimport { Decoration } from \"@codemirror/view\";\nimport { commentUI } from \"./lib/extensions/commentUI.ts\";\nimport { cursor } from \"@automerge/automerge-repo/slim\";\nimport { getRegistry } from \"@inkandswitch/patchwork-plugins\";\nimport { subscribeDoc, subscribe } from \"@inkandswitch/patchwork-providers-solid\";\nimport { createMemo, createResource, Show } from \"solid-js\";\nimport { createCommentForRange } from \"./lib/extensions/comments.ts\";\nconst PATH = [\"content\"];\nexport function CodeMirrorEditor(props) {\n const baseline = subscribe(props.element, {\n type: \"draft:baseline\",\n url: props.handle.url\n }, {\n heads: null\n });\n const commentEntries = subscribe(props.element, {\n type: \"patchwork:comments\"\n }, []);\n const [focusDoc, focusHandle] = subscribeDoc(props.element, {\n type: \"patchwork:focus\"\n });\n const contactUrl = subscribe(props.element, {\n type: \"patchwork:contact\"\n });\n const [commentTargets] = createResource(commentEntries, entries => getDedupedCommentTargets(entries, props.handle.url, props.repo), {\n initialValue: []\n });\n const focusRefUrls = createMemo(() => {\n const doc = focusDoc();\n return [...Object.keys(doc?.selection ?? {}), ...Object.keys(doc?.highlight ?? {})];\n });\n const [emphasisTargets] = createResource(focusRefUrls, async urls => resolveSubDocUrlsOfDoc(urls, props.handle.url, props.repo), {\n initialValue: []\n });\n const scrollTarget = createMemo(() => {\n let from = Infinity;\n let to = -Infinity;\n for (const ref of emphasisTargets()) {\n const positions = ref.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n from = Math.min(from, start);\n to = Math.max(to, end);\n }\n return from <= to ? [from, to] : null;\n });\n let lastEmittedUrl;\n const onChangeSelection = (from, to) => {\n const handle = focusHandle();\n if (!handle) return;\n const nextUrl = props.handle.sub(...PATH, cursor(from, to)).url;\n if (nextUrl === lastEmittedUrl) return;\n handle.change(doc => {\n doc.selection = {\n [nextUrl]: true\n };\n });\n lastEmittedUrl = nextUrl;\n };\n const decorations = () => {\n const targetRefs = commentTargets();\n const emphasisRefs = emphasisTargets();\n return RangeSet.of(buildCommentDecorations(targetRefs, emphasisRefs), true\n // sort ranges\n );\n };\n const createThreadForRange = (from, to) => {\n const url = contactUrl();\n if (!url) {\n console.warn(\"Cannot create comment: no contactUrl available\");\n return null;\n }\n return createCommentForRange(props.handle, PATH, from, to, url);\n };\n const threadAtPos = pos => {\n const threadByTarget = /* @__PURE__ */new Map();\n for (const entry of commentEntries()) {\n threadByTarget.set(entry.targetUrl, entry.threadUrl);\n }\n for (const handle of commentTargets()) {\n const positions = handle.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n if (start === end || pos < start || pos > end) continue;\n const threadUrl = threadByTarget.get(handle.url);\n if (threadUrl) return {\n threadUrl,\n from: start\n };\n }\n return null;\n };\n const findThreadByUrl = threadUrl => {\n const threads = props.handle.doc()?.[\"@comments\"]?.threads ?? [];\n return threads.find(thread => props.handle.sub(\"@comments\", \"threads\", {\n id: thread.id\n }).url === threadUrl);\n };\n const watchThreadForClose = (threadUrl, close) => {\n const contact = contactUrl();\n const hasOwnDraft = thread => thread.comments.some(c => c.contactUrl === contact && (c.draftContent !== void 0 || c.content === void 0));\n const seed = findThreadByUrl(threadUrl);\n let sawDraft = seed ? hasOwnDraft(seed) : false;\n const onChange = () => {\n const thread = findThreadByUrl(threadUrl);\n if (!thread) return close();\n if (hasOwnDraft(thread)) sawDraft = true;else if (sawDraft) close();\n };\n props.handle.on(\"change\", onChange);\n return () => props.handle.off(\"change\", onChange);\n };\n const onClosePopover = threadUrl => {\n const contact = contactUrl();\n const isEmptyOwnDraft = c => c.contactUrl === contact && c.content === void 0 && (c.draftContent === void 0 || c.draftContent.trim() === \"\");\n const thread = findThreadByUrl(threadUrl);\n if (!thread || !thread.comments.some(isEmptyOwnDraft)) return;\n const threadId = thread.id;\n queueMicrotask(() => {\n props.handle.change(doc => {\n const list = doc[\"@comments\"]?.threads;\n if (!list) return;\n const index = list.findIndex(t => t.id === threadId);\n if (index === -1) return;\n const target = list[index];\n for (let i = target.comments.length - 1; i >= 0; i--) {\n if (isEmptyOwnDraft(target.comments[i])) target.comments.splice(i, 1);\n }\n if (target.comments.length === 0) list.splice(index, 1);\n });\n });\n };\n const [datatypeExtensions] = createResource(() => loadCodeMirrorExtensionsForDoc(props.handle));\n const extensions = () => [commentUI({\n createThreadForRange,\n threadAtPos,\n watchThreadForClose,\n onClose: onClosePopover\n }), ...(datatypeExtensions() ?? [])];\n return (() => {\n var _el$ = _tmpl$(),\n _el$2 = _el$.firstChild,\n _el$3 = _el$2.firstChild;\n _$insert(_el$3, _$createComponent(Show, {\n get when() {\n return datatypeExtensions.state === \"ready\";\n },\n get children() {\n return _$createComponent(CodeMirror, {\n get handle() {\n return props.handle;\n },\n get repo() {\n return props.repo;\n },\n path: PATH,\n decorations: decorations,\n baseline: () => baseline()?.heads ?? null,\n get extensions() {\n return extensions();\n },\n onChangeSelection: onChangeSelection,\n scrollTarget: scrollTarget,\n contactUrl: () => contactUrl() ?? null\n });\n }\n }));\n return _el$;\n })();\n}\nexport const mount = (handle, element) => render(() => _$createComponent(CodeMirrorEditor, {\n handle: handle,\n get repo() {\n return element.repo;\n },\n element: element\n}), element);\nasync function getDedupedCommentTargets(comments, docUrl, repo) {\n if (!comments) return [];\n const overlappingRefs = /* @__PURE__ */new Set();\n for (const comment of comments) {\n if (comment.targetUrl.startsWith(docUrl)) {\n const target = await repo.find(comment.targetUrl);\n overlappingRefs.add(target);\n }\n }\n return Array.from(overlappingRefs);\n}\nasync function resolveSubDocUrlsOfDoc(urls, docUrl, repo) {\n const refs = /* @__PURE__ */new Set();\n for (const url of urls) {\n if (url.startsWith(docUrl)) {\n refs.add(await repo.find(url));\n }\n }\n return Array.from(refs);\n}\nfunction buildCommentDecorations(targetRefs, emphasisRefs) {\n const out = [];\n for (const ref of targetRefs) {\n const positions = ref.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n if (start === end) continue;\n const isEmphasised = emphasisRefs.some(s => s.overlaps(ref));\n out.push(Decoration.mark({\n attributes: {\n style: commentTargetStyle(isEmphasised)\n }\n }).range(start, end));\n }\n return out;\n}\nfunction commentTargetStyle(isEmphasised) {\n const paper = isEmphasised ? \"56%\" : \"82%\";\n return `\n border-bottom: 2px solid var(--text-editor-secondary-text);\n background-color: color-mix(in oklch, var(--studio-secondary), var(--text-editor-fill) ${paper});\n `;\n}\nasync function loadCodeMirrorExtensionsForDoc(handle) {\n const docType = handle.doc()?.[\"@patchwork\"]?.type;\n const registry = getRegistry(\"codemirror:extension\");\n const loaded = await registry.loadAll(registry.filter(ext => {\n return ext.supportedDatatypes === \"*\" || Array.isArray(ext.supportedDatatypes) && ext.supportedDatatypes.includes(docType);\n }));\n return loaded.flatMap(ext => {\n const impl = ext.module;\n return Array.isArray(impl) ? impl : [impl];\n });\n}", "import {\n Extension,\n StateEffect,\n StateField,\n type EditorState,\n} from \"@codemirror/state\";\nimport {\n EditorView,\n showTooltip,\n type Tooltip,\n type TooltipView,\n} from \"@codemirror/view\";\nimport type { AutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { COMMENT_THREAD_TYPE } from \"./comments.ts\";\n\n// The data hooks the editor UI needs from its host (the CodeMirror tool). The\n// extension owns all the DOM/positioning/lifecycle; the host owns everything\n// that needs the repo, the current contact, or the comment index.\nexport type CommentUIOptions = {\n // Create a thread targeting [from, to] and return its url, or null if it\n // couldn't be created (e.g. no current contact). Called when the floating\n // \"Comment\" button is clicked.\n createThreadForRange: (from: number, to: number) => AutomergeUrl | null;\n // Map a document position to the thread whose commented range covers it (and\n // that range's start), or null. Called when the user clicks commented text so\n // we can pop that thread open under the range's start.\n threadAtPos: (\n pos: number\n ) => { threadUrl: AutomergeUrl; from: number } | null;\n // Watch a thread and invoke `close` once its pending draft is submitted (or\n // cancelled, or the thread is removed). Returns an unsubscribe. Lets the\n // popover dismiss itself the moment the comment is saved.\n watchThreadForClose: (threadUrl: AutomergeUrl, close: () => void) => () => void;\n // Called whenever a popover closes, for any reason. Lets the host discard an\n // abandoned, never-filled-in draft so dismissing the popover doesn't litter\n // the document with empty comments.\n onClose: (threadUrl: AutomergeUrl) => void;\n};\n\n// Which thread the popover is showing, and the document position it hangs off.\ntype PopoverState = { threadUrl: AutomergeUrl; anchor: number };\n\nconst openPopover = StateEffect.define<PopoverState>();\nconst closePopover = StateEffect.define<null>();\n// Hide the floating \"Comment\" button until the next selection change (Escape).\nconst dismissButton = StateEffect.define<null>();\n\n// Mutable UI state shared across the extension's pieces (not editor state\n// because it isn't versioned/undoable): the thread we most recently dismissed,\n// so a click that stays inside it doesn't immediately pop it back open.\ntype CommentUIState = { suppressed: AutomergeUrl | null };\n\nconst POPOVER_CLASS = \"cm-comment-popover\";\n\n// A speech-bubble icon + \"Comment\" label, shown floating above the selection.\nconst buildCommentButton = (\n from: number,\n to: number,\n head: number,\n view: EditorView,\n options: CommentUIOptions\n): HTMLElement => {\n const button = document.createElement(\"button\");\n button.className = \"cm-comment-button\";\n button.setAttribute(\"aria-label\", \"Comment\");\n\n button.innerHTML = `\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"\n fill=\"currentColor\" stroke=\"none\"/>\n </svg>\n <span>Comment</span>\n `;\n\n // Without this, the browser focuses the button on mousedown, the editor\n // blurs, the selection collapses, our tooltip source returns null, and\n // CodeMirror tears down the button before `click` fires \u2014 so the first\n // press silently does nothing and the user has to click twice. Swallowing\n // mousedown keeps the editor focused and the selection (and button) alive\n // long enough for the click to land.\n button.addEventListener(\"mousedown\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n });\n\n button.addEventListener(\"click\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n const threadUrl = options.createThreadForRange(from, to);\n if (!threadUrl) return;\n // Open the popover right where the button was (the selection head).\n view.dispatch({ effects: openPopover.of({ threadUrl, anchor: head }) });\n });\n\n return button;\n};\n\n// The popover body: the shared comment-thread tool, rendered inline via the\n// `patchwork-view` custom element. It's mounted inside `view.dom`, so the\n// element's provider events (repo, contact, \u2026) still bubble up to the host.\nconst buildThreadPopover = (\n view: EditorView,\n state: PopoverState,\n options: CommentUIOptions,\n ui: CommentUIState\n): TooltipView => {\n const dom = document.createElement(\"div\");\n dom.className = POPOVER_CLASS;\n\n const threadView = document.createElement(\"patchwork-view\");\n threadView.setAttribute(\"doc-url\", state.threadUrl);\n threadView.setAttribute(\"tool-id\", COMMENT_THREAD_TYPE);\n dom.append(threadView);\n\n const close = () => view.dispatch({ effects: closePopover.of(null) });\n\n // Dismiss on a click anywhere outside the popover, or on Escape. Both are\n // armed on a deferred tick so the very interaction that opened the popover\n // (the button click / the click on commented text) doesn't also close it.\n const onOutsideMouseDown = (event: MouseEvent) => {\n if (!dom.contains(event.target as Node)) close();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") close();\n };\n const doc = view.win.document;\n const armTimer = view.win.setTimeout(() => {\n doc.addEventListener(\"mousedown\", onOutsideMouseDown, true);\n doc.addEventListener(\"keydown\", onKeyDown, true);\n }, 0);\n\n // Close as soon as the draft is submitted (or the thread goes away).\n const stopWatch = options.watchThreadForClose(state.threadUrl, close);\n\n return {\n dom,\n destroy() {\n view.win.clearTimeout(armTimer);\n doc.removeEventListener(\"mousedown\", onOutsideMouseDown, true);\n doc.removeEventListener(\"keydown\", onKeyDown, true);\n stopWatch();\n // Remember this thread as dismissed so clicking within its range again\n // doesn't immediately reopen it (cleared once a click lands elsewhere).\n ui.suppressed = state.threadUrl;\n options.onClose(state.threadUrl);\n },\n };\n};\n\nconst buttonTooltip = (\n state: EditorState,\n options: CommentUIOptions\n): Tooltip | null => {\n const sel = state.selection.main;\n // A read-only editor can't take the comment write, so don't offer the button.\n if (sel.empty || state.readOnly) return null;\n\n // Anchor the button at the drag tail (the selection head) rather than the\n // start, so dragging left-to-right lands it on the right and right-to-left\n // on the left.\n return {\n pos: sel.head,\n above: true,\n strictSide: false,\n arrow: false,\n create: (view) => ({\n dom: buildCommentButton(sel.from, sel.to, sel.head, view, options),\n }),\n };\n};\n\nconst popoverTooltip = (\n state: PopoverState,\n options: CommentUIOptions,\n ui: CommentUIState\n): Tooltip => ({\n pos: state.anchor,\n above: false, // comes out underneath the anchor\n strictSide: false,\n arrow: false,\n create: (view) => buildThreadPopover(view, state, options, ui),\n});\n\ntype FieldValue = {\n popover: PopoverState | null;\n // The floating button is hidden while this is true \u2014 after Escape, or after a\n // comment is added \u2014 until a fresh selection re-arms it.\n buttonDismissed: boolean;\n tooltip: Tooltip | null;\n};\n\n// A single field driving the one tooltip slot: the thread popover when one is\n// open, otherwise the selection's \"Comment\" button.\nconst commentUIField = (options: CommentUIOptions, ui: CommentUIState) =>\n StateField.define<FieldValue>({\n create: (state) => ({\n popover: null,\n buttonDismissed: false,\n tooltip: buttonTooltip(state, options),\n }),\n update(value, tr) {\n let popover = value.popover;\n let buttonDismissed = value.buttonDismissed;\n let popoverChanged = false;\n\n // Read-only can flip mid-session: entering it closes any open popover \u2014\n // its reply box writes to a doc that rejects writes \u2014 and the flip\n // itself must recompute the tooltip so the button hides/re-arms.\n const readOnlyChanged = tr.startState.readOnly !== tr.state.readOnly;\n if (readOnlyChanged && tr.state.readOnly && popover) {\n popover = null;\n popoverChanged = true;\n }\n\n // Keep the popover pinned to its text as the document shifts under it.\n if (popover && tr.docChanged) {\n popover = { ...popover, anchor: tr.changes.mapPos(popover.anchor) };\n popoverChanged = true;\n }\n for (const effect of tr.effects) {\n if (effect.is(openPopover)) {\n popover = effect.value;\n popoverChanged = true;\n } else if (effect.is(closePopover)) {\n popover = null;\n popoverChanged = true;\n // A comment was just added (or dismissed): don't flash the button\n // back up over the still-selected text.\n buttonDismissed = true;\n } else if (effect.is(dismissButton)) {\n buttonDismissed = true;\n }\n }\n // Any fresh selection re-arms the button.\n if (tr.selection) buttonDismissed = false;\n\n if (popover) {\n // Reuse the exact same tooltip object unless the popover state itself\n // changed. Otherwise an unrelated transaction (a stray selection\n // change, an extension reconfigure, \u2026) would hand CodeMirror a fresh\n // Tooltip and it would tear down and remount the thread view mid-edit.\n if (!popoverChanged) {\n return buttonDismissed === value.buttonDismissed\n ? value\n : { ...value, buttonDismissed };\n }\n return {\n popover,\n buttonDismissed,\n tooltip: popoverTooltip(popover, options, ui),\n };\n }\n\n // No popover: show the selection button, unless it's been dismissed.\n // Recompute only when something relevant changed.\n if (\n !popoverChanged &&\n !tr.docChanged &&\n !tr.selection &&\n !readOnlyChanged &&\n buttonDismissed === value.buttonDismissed\n ) {\n return value;\n }\n return {\n popover: null,\n buttonDismissed,\n tooltip: buttonDismissed ? null : buttonTooltip(tr.state, options),\n };\n },\n provide: (field) => showTooltip.from(field, (value) => value.tooltip),\n });\n\n// Editor-level interactions: click commented text to (re)open its thread, and\n// Escape to dismiss the floating button.\nconst commentInteractions = (\n options: CommentUIOptions,\n ui: CommentUIState,\n field: StateField<FieldValue>\n): Extension =>\n EditorView.domEventHandlers({\n mousedown(event, view) {\n if (\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.altKey ||\n event.shiftKey\n ) {\n return false;\n }\n // Clicks inside an open popover are the thread tool's own business.\n if ((event.target as HTMLElement | null)?.closest(\".\" + POPOVER_CLASS)) {\n return false;\n }\n // Read-only: don't open threads either \u2014 the popover's reply box would\n // write into a doc that rejects writes.\n if (view.state.readOnly) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n const hit = options.threadAtPos(pos);\n if (!hit) {\n ui.suppressed = null; // clicked away from any comment; re-arm reopening\n return false;\n }\n // Don't reopen the thread we just dismissed while the click stays inside\n // it \u2014 the user has to click elsewhere (or another comment) first.\n if (hit.threadUrl === ui.suppressed) return false;\n ui.suppressed = null;\n view.dispatch({\n effects: openPopover.of({ threadUrl: hit.threadUrl, anchor: hit.from }),\n });\n return false; // let the click also move the cursor as usual\n },\n keydown(event, view) {\n if (event.key !== \"Escape\") return false;\n const state = view.state.field(field, false);\n // While a popover is open its own Escape handler wins (it also works when\n // the popover's textarea holds focus); here we only hide the button.\n if (!state || state.popover || !state.tooltip) return false;\n view.dispatch({ effects: dismissButton.of(null) });\n return true;\n },\n });\n\n// Global colours are lifted into local --cm-comment-* tokens in a\n// :root/:host/[theme] block so they re-evaluate when the theme swaps. This\n// derivation can't live in the baseTheme below \u2014 CodeMirror prefixes every\n// non-\"&\" selector with the editor's scope class, so a `:root` key would\n// become `.cm... :root` and never match. It's injected as a plain stylesheet\n// instead, and the button reads only the derived tokens. The button is editor\n// UI, so it derives from --editor-* (not --studio-*, which is studio chrome).\nconst COMMENT_BUTTON_VARS_ID = \"cm-comment-button-vars\";\nconst ensureCommentButtonVars = (): void => {\n if (\n typeof document === \"undefined\" ||\n document.getElementById(COMMENT_BUTTON_VARS_ID)\n ) {\n return;\n }\n const style = document.createElement(\"style\");\n style.id = COMMENT_BUTTON_VARS_ID;\n style.textContent = `\n :root, :host, [theme] {\n /* the button sits on the editor surface: bg is the plain editor fill,\n text is the editor ink, and hover lifts a gentle one step up the fill\n ramp. see --text-editor-* in the theme. */\n --cm-comment-button-fg: var(--text-editor-line, #0a2b23);\n --cm-comment-button-bg: var(--text-editor-fill, #35f7ca);\n --cm-comment-button-bg-hover: var(--text-editor-fill-offset-10, #2ce0b6);\n }\n `;\n document.head.append(style);\n};\n\nconst commentUITheme = EditorView.baseTheme({\n \".cm-tooltip:has(.cm-comment-button)\": {\n border: \"none\",\n background: \"transparent\",\n boxShadow: \"none\",\n },\n \".cm-comment-button\": {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"5px\",\n padding: \"4px 9px\",\n font: \"inherit\",\n fontSize: \"12px\",\n fontWeight: \"500\",\n lineHeight: \"1\",\n color: \"var(--cm-comment-button-fg)\",\n background: \"var(--cm-comment-button-bg)\",\n border: \"none\",\n borderRadius: \"6px\",\n boxShadow: \"0 2px 8px rgba(0, 0, 0, 0.25)\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n userSelect: \"none\",\n whiteSpace: \"nowrap\",\n transition: \"background-color max(1ms, calc(0.15s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))\",\n },\n \".cm-comment-button:hover\": {\n background: \"var(--cm-comment-button-bg-hover)\",\n },\n \".cm-comment-button svg\": {\n display: \"block\",\n },\n // The thread tool carries its own card (background, border, padding), so the\n // tooltip wrapper is just a transparent, softly-lifted host for it.\n \".cm-tooltip:has(.cm-comment-popover)\": {\n border: \"none\",\n background: \"transparent\",\n boxShadow: \"none\",\n },\n \".cm-comment-popover\": {\n width: \"min(360px, 92vw)\",\n marginTop: \"6px\",\n fontFamily: \"var(--editor-family)\",\n filter: \"drop-shadow(0 4px 14px rgba(0, 0, 0, 0.25))\",\n },\n // The popover supplies its own frame (the tooltip wrapper's shadow), so the\n // thread card inside drops its own border and shadow to avoid doubling up.\n \".cm-comment-popover .comments-thread-card\": {\n border: \"0\",\n boxShadow: \"none\",\n },\n // The draft textarea is width:100% with its own padding + border; without\n // border-box it spills out of the card's right edge (the card's own reset is\n // scoped to the sidebar panel, which the popover isn't inside).\n \".cm-comment-popover .comment-draft-editor\": {\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n minWidth: \"0\",\n width: \"100%\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-content\": {\n minHeight: \"0\",\n minWidth: \"0\",\n margin: \"0\",\n padding: \"8px\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-line\": {\n margin: \"0\",\n padding: \"0\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-scroller\": {\n minWidth: \"0\",\n },\n});\n\n// The comment UI: the floating \"Comment\" button on a selection, and the thread\n// popover it (and clicking commented text) opens.\nexport const commentUI = (options: CommentUIOptions): Extension => {\n ensureCommentButtonVars();\n const ui: CommentUIState = { suppressed: null };\n const field = commentUIField(options, ui);\n return [field, commentInteractions(options, ui, field), commentUITheme];\n};\n", "import {\n cursor,\n type AutomergeUrl,\n type DocHandle,\n} from \"@automerge/automerge-repo/slim\";\n\nexport type DocWithComments = {\n \"@comments\"?: {\n threads: CommentThread[];\n };\n};\n\n/**\n * The `@patchwork.type` stamped on a comment thread so a datatype (and thus a\n * relevant tool) can be resolved when the thread's subdocument is opened on its\n * own. Mirrors `COMMENT_THREAD_TYPE` in the comments-view module.\n */\nexport const COMMENT_THREAD_TYPE = \"comment-thread\";\n\nexport type CommentThread = {\n id: string;\n refs: AutomergeUrl[];\n isResolved: boolean;\n comments: Comment[];\n \"@patchwork\"?: { type: string; title?: string };\n};\n\nexport type Comment = {\n id: string;\n content?: string;\n draftContent?: string;\n contactUrl: AutomergeUrl;\n timestamp: number;\n};\n\n// Creates a thread (with one empty, unfilled comment) targeting [from, to] and\n// returns the thread's url so the caller can open it \u2014 e.g. in a popover \u2014 for\n// the author to fill in. An abandoned, never-filled comment should be cleaned\n// up by the caller (see the comment UI's `onClose`).\nexport function createCommentForRange(\n handle: DocHandle<unknown>,\n path: readonly string[],\n from: number,\n to: number,\n contactUrl: AutomergeUrl\n): AutomergeUrl {\n const targetUrl = handle.sub(...path, cursor(from, to)).url;\n const threadId = crypto.randomUUID();\n const commentId = crypto.randomUUID();\n\n handle.change((doc: DocWithComments) => {\n doc[\"@comments\"] ??= { threads: [] };\n doc[\"@comments\"].threads.push({\n id: threadId,\n refs: [targetUrl],\n isResolved: false,\n comments: [\n {\n id: commentId,\n contactUrl,\n timestamp: Date.now(),\n },\n ],\n \"@patchwork\": { type: COMMENT_THREAD_TYPE },\n });\n });\n\n return handle.sub(\"@comments\", \"threads\", { id: threadId }).url;\n}\n", "import { createEffect, createSignal, onCleanup, onMount, } from \"solid-js\";\nimport { createStore, reconcile } from \"solid-js/store\";\nimport * as Providers from \"@inkandswitch/patchwork-providers\";\nfunction resolveElement(source) {\n return typeof source === \"function\" ? source() : source;\n}\n/**\n * Generic reactive request. Resolves the first value a provider emits for\n * `selector` (via the one-shot `request` helper) and returns an accessor that\n * reads `undefined` until then. `T` is the response type.\n */\nexport function request(element, selector) {\n const [value, setValue] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n Providers.request(target, selector).then((v) => {\n if (v == null)\n return;\n setValue(() => v);\n });\n });\n return value;\n}\nexport function subscribe(element, selector, initialValue) {\n const [value, setValue] = createSignal(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, setValue);\n onCleanup(unsubscribe);\n });\n return value;\n}\n/**\n * Store-backed subscription. Use this when a consumer wants Solid's\n * fine-grained nested reactivity for incoming JSON object or array snapshots.\n * Requires an initial object/array so the store has a stable root.\n *\n * `reconcile` preserves stable object/array identity where possible, so this\n * helper is not a good fit when the top-level value is used as an identity key\n * for resources or memos.\n */\nexport function subscribeReconciled(element, selector, initialValue) {\n const [store, setStore] = createStore(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, (v) => {\n setStore(reconcile(v));\n });\n onCleanup(unsubscribe);\n });\n return store;\n}\n/**\n * Handle-specialized subscription. Use when the answering provider emits an\n * `AutomergeUrl`. The handle is recovered locally from the global repo\n * (`window.repo`), so it stays fully live \u2014 reads project reactively and writes go\n * straight back to the same repo. Returns `[doc, handle]` matching the shape\n * of `solid-automerge`'s `useDocument`; both read `undefined` until the first\n * url arrives. `T` is the doc shape inside the handle.\n */\nexport function subscribeDoc(element, selector) {\n const [handle, setHandle] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n let canceled = false;\n const unsubscribe = Providers.subscribe(target, selector, (url) => {\n if (!url)\n return;\n const repo = \"repo\" in window ? window.repo : undefined;\n if (!repo)\n return;\n void Promise.resolve(repo.find(url)).then((h) => {\n if (canceled)\n return;\n setHandle(() => h);\n });\n });\n onCleanup(() => {\n canceled = true;\n unsubscribe();\n });\n });\n // Mirror the doc into a store by `reconcile`-ing against the *materialized*\n // snapshot (`handle.doc()`) on every change, rather than replaying the\n // change's incremental patches (as `solid-automerge`'s `createDocumentProjection`\n // / `autoproduce` does).\n //\n // A whole-value write such as `doc.list = [...]` is lowered by Automerge to\n // `putObjectFromHydrate`, whose patch stream is a `put` that materializes the\n // new container *plus* per-element `insert` patches. Replaying that delta with\n // `applyPatches` double-applies the contents \u2014 the `put` seeds `[a, b]`, then\n // the inserts splice `a, b` in again, yielding `[a, b, a, b]` \u2014 even though the\n // document itself is correct. The materialized snapshot is always\n // authoritative, so reconciling against it is robust to any valid change,\n // whole-array reassignment included.\n const [store, setStore] = createStore({});\n const [ready, setReady] = createSignal(false);\n createEffect(() => {\n const h = handle();\n if (!h) {\n setReady(false);\n return;\n }\n const sync = () => {\n setStore(reconcile((h.doc() ?? {})));\n setReady(true);\n };\n sync();\n h.on(\"change\", sync);\n onCleanup(() => h.off(\"change\", sync));\n });\n const doc = () => (ready() ? store : undefined);\n return [doc, handle];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { template as _$template } from \"solid-js/web\";\nimport { insert as _$insert } from \"solid-js/web\";\nimport { createComponent as _$createComponent } from \"solid-js/web\";\nvar _tmpl$ = /*#__PURE__*/_$template(`<div style=\"width:100%;height:100%;overflow:hidden;position:relative;background:var(--studio-fill, white)\"><div style=display:flex;height:100%><div style=position:relative;flex:1;height:100%>`);\nimport { CodeMirror } from \"./lib/codemirror.tsx\";\nimport { render } from \"solid-js/web\";\nimport { RangeSet } from \"@codemirror/state\";\nimport { Decoration } from \"@codemirror/view\";\nimport { commentUI } from \"./lib/extensions/commentUI.ts\";\nimport { cursor } from \"@automerge/automerge-repo/slim\";\nimport { getRegistry } from \"@inkandswitch/patchwork-plugins\";\nimport { subscribeDoc, subscribe } from \"@inkandswitch/patchwork-providers-solid\";\nimport { createMemo, createResource, Show } from \"solid-js\";\nimport { createCommentForRange } from \"./lib/extensions/comments.ts\";\nconst PATH = [\"content\"];\nexport function CodeMirrorEditor(props) {\n const baseline = subscribe(props.element, {\n type: \"draft:baseline\",\n url: props.handle.url\n }, {\n heads: null\n });\n const commentEntries = subscribe(props.element, {\n type: \"patchwork:comments\"\n }, []);\n const [focusDoc, focusHandle] = subscribeDoc(props.element, {\n type: \"patchwork:focus\"\n });\n const contactUrl = subscribe(props.element, {\n type: \"patchwork:contact\"\n });\n const [commentTargets] = createResource(commentEntries, entries => getDedupedCommentTargets(entries, props.handle.url, props.repo), {\n initialValue: []\n });\n const focusRefUrls = createMemo(() => {\n const doc = focusDoc();\n return [...Object.keys(doc?.selection ?? {}), ...Object.keys(doc?.highlight ?? {})];\n });\n const [emphasisTargets] = createResource(focusRefUrls, async urls => resolveSubDocUrlsOfDoc(urls, props.handle.url, props.repo), {\n initialValue: []\n });\n const scrollTarget = createMemo(() => {\n let from = Infinity;\n let to = -Infinity;\n for (const ref of emphasisTargets()) {\n const positions = ref.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n from = Math.min(from, start);\n to = Math.max(to, end);\n }\n return from <= to ? [from, to] : null;\n });\n let lastEmittedUrl;\n const onChangeSelection = (from, to) => {\n const handle = focusHandle();\n if (!handle) return;\n const nextUrl = props.handle.sub(...PATH, cursor(from, to)).url;\n if (nextUrl === lastEmittedUrl) return;\n handle.change(doc => {\n doc.selection = {\n [nextUrl]: true\n };\n });\n lastEmittedUrl = nextUrl;\n };\n const decorations = () => {\n const targetRefs = commentTargets();\n const emphasisRefs = emphasisTargets();\n return RangeSet.of(buildCommentDecorations(targetRefs, emphasisRefs), true\n // sort ranges\n );\n };\n const createThreadForRange = (from, to) => {\n const url = contactUrl();\n if (!url) {\n console.warn(\"Cannot create comment: no contactUrl available\");\n return null;\n }\n return createCommentForRange(props.handle, PATH, from, to, url);\n };\n const threadAtPos = pos => {\n const threadByTarget = /* @__PURE__ */new Map();\n for (const entry of commentEntries()) {\n threadByTarget.set(entry.targetUrl, entry.threadUrl);\n }\n for (const handle of commentTargets()) {\n const positions = handle.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n if (start === end || pos < start || pos > end) continue;\n const threadUrl = threadByTarget.get(handle.url);\n if (threadUrl) return {\n threadUrl,\n from: start\n };\n }\n return null;\n };\n const findThreadByUrl = threadUrl => {\n const threads = props.handle.doc()?.[\"@comments\"]?.threads ?? [];\n return threads.find(thread => props.handle.sub(\"@comments\", \"threads\", {\n id: thread.id\n }).url === threadUrl);\n };\n const watchThreadForClose = (threadUrl, close) => {\n const contact = contactUrl();\n const hasOwnDraft = thread => thread.comments.some(c => c.contactUrl === contact && (c.draftContent !== void 0 || c.content === void 0));\n const seed = findThreadByUrl(threadUrl);\n let sawDraft = seed ? hasOwnDraft(seed) : false;\n const onChange = () => {\n const thread = findThreadByUrl(threadUrl);\n if (!thread) return close();\n if (hasOwnDraft(thread)) sawDraft = true;else if (sawDraft) close();\n };\n props.handle.on(\"change\", onChange);\n return () => props.handle.off(\"change\", onChange);\n };\n const onClosePopover = threadUrl => {\n const contact = contactUrl();\n const isEmptyOwnDraft = c => c.contactUrl === contact && c.content === void 0 && (c.draftContent === void 0 || c.draftContent.trim() === \"\");\n const thread = findThreadByUrl(threadUrl);\n if (!thread || !thread.comments.some(isEmptyOwnDraft)) return;\n const threadId = thread.id;\n queueMicrotask(() => {\n props.handle.change(doc => {\n const list = doc[\"@comments\"]?.threads;\n if (!list) return;\n const index = list.findIndex(t => t.id === threadId);\n if (index === -1) return;\n const target = list[index];\n for (let i = target.comments.length - 1; i >= 0; i--) {\n if (isEmptyOwnDraft(target.comments[i])) target.comments.splice(i, 1);\n }\n if (target.comments.length === 0) list.splice(index, 1);\n });\n });\n };\n const [datatypeExtensions] = createResource(() => loadCodeMirrorExtensionsForDoc(props.handle));\n const extensions = () => [commentUI({\n createThreadForRange,\n threadAtPos,\n watchThreadForClose,\n onClose: onClosePopover\n }), ...(datatypeExtensions() ?? [])];\n return (() => {\n var _el$ = _tmpl$(),\n _el$2 = _el$.firstChild,\n _el$3 = _el$2.firstChild;\n _$insert(_el$3, _$createComponent(Show, {\n get when() {\n return datatypeExtensions.state === \"ready\";\n },\n get children() {\n return _$createComponent(CodeMirror, {\n get handle() {\n return props.handle;\n },\n get repo() {\n return props.repo;\n },\n path: PATH,\n decorations: decorations,\n baseline: () => baseline()?.heads ?? null,\n get extensions() {\n return extensions();\n },\n onChangeSelection: onChangeSelection,\n scrollTarget: scrollTarget,\n contactUrl: () => contactUrl() ?? null\n });\n }\n }));\n return _el$;\n })();\n}\nexport const mount = (handle, element) => render(() => _$createComponent(CodeMirrorEditor, {\n handle: handle,\n get repo() {\n return element.repo;\n },\n element: element\n}), element);\nasync function getDedupedCommentTargets(comments, docUrl, repo) {\n if (!comments) return [];\n const overlappingRefs = /* @__PURE__ */new Set();\n for (const comment of comments) {\n if (comment.targetUrl.startsWith(docUrl)) {\n const target = await repo.find(comment.targetUrl);\n overlappingRefs.add(target);\n }\n }\n return Array.from(overlappingRefs);\n}\nasync function resolveSubDocUrlsOfDoc(urls, docUrl, repo) {\n const refs = /* @__PURE__ */new Set();\n for (const url of urls) {\n if (url.startsWith(docUrl)) {\n refs.add(await repo.find(url));\n }\n }\n return Array.from(refs);\n}\nfunction buildCommentDecorations(targetRefs, emphasisRefs) {\n const out = [];\n for (const ref of targetRefs) {\n const positions = ref.rangePositions();\n if (!positions) continue;\n const [start, end] = positions;\n if (start === end) continue;\n const isEmphasised = emphasisRefs.some(s => s.overlaps(ref));\n out.push(Decoration.mark({\n attributes: {\n style: commentTargetStyle(isEmphasised)\n }\n }).range(start, end));\n }\n return out;\n}\nfunction commentTargetStyle(isEmphasised) {\n const paper = isEmphasised ? \"56%\" : \"82%\";\n return `\n border-bottom: 2px solid var(--text-editor-secondary-text);\n background-color: color-mix(in oklch, var(--studio-secondary), var(--text-editor-fill) ${paper});\n `;\n}\nasync function loadCodeMirrorExtensionsForDoc(handle) {\n const docType = handle.doc()?.[\"@patchwork\"]?.type;\n const registry = getRegistry(\"codemirror:extension\");\n const loaded = await registry.loadAll(registry.filter(ext => {\n return ext.supportedDatatypes === \"*\" || Array.isArray(ext.supportedDatatypes) && ext.supportedDatatypes.includes(docType);\n }));\n return loaded.flatMap(ext => {\n const impl = ext.module;\n return Array.isArray(impl) ? impl : [impl];\n });\n}", "import {\n Extension,\n StateEffect,\n StateField,\n type EditorState,\n} from \"@codemirror/state\";\nimport {\n EditorView,\n showTooltip,\n type Tooltip,\n type TooltipView,\n} from \"@codemirror/view\";\nimport type { AutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { COMMENT_THREAD_TYPE } from \"./comments.ts\";\n\n// The data hooks the editor UI needs from its host (the CodeMirror tool). The\n// extension owns all the DOM/positioning/lifecycle; the host owns everything\n// that needs the repo, the current contact, or the comment index.\nexport type CommentUIOptions = {\n // Create a thread targeting [from, to] and return its url, or null if it\n // couldn't be created (e.g. no current contact). Called when the floating\n // \"Comment\" button is clicked.\n createThreadForRange: (from: number, to: number) => AutomergeUrl | null;\n // Map a document position to the thread whose commented range covers it (and\n // that range's start), or null. Called when the user clicks commented text so\n // we can pop that thread open under the range's start.\n threadAtPos: (\n pos: number\n ) => { threadUrl: AutomergeUrl; from: number } | null;\n // Watch a thread and invoke `close` once its pending draft is submitted (or\n // cancelled, or the thread is removed). Returns an unsubscribe. Lets the\n // popover dismiss itself the moment the comment is saved.\n watchThreadForClose: (threadUrl: AutomergeUrl, close: () => void) => () => void;\n // Called whenever a popover closes, for any reason. Lets the host discard an\n // abandoned, never-filled-in draft so dismissing the popover doesn't litter\n // the document with empty comments.\n onClose: (threadUrl: AutomergeUrl) => void;\n};\n\n// Which thread the popover is showing, and the document position it hangs off.\ntype PopoverState = { threadUrl: AutomergeUrl; anchor: number };\n\nconst openPopover = StateEffect.define<PopoverState>();\nconst closePopover = StateEffect.define<null>();\n// Hide the floating \"Comment\" button until the next selection change (Escape).\nconst dismissButton = StateEffect.define<null>();\n// The main mouse button went down (true) or up (false) in the editor. The\n// button waits for the mouse to come up: it doesn't chase a selection that's\n// still being dragged out.\nconst setPointerDown = StateEffect.define<boolean>();\n\n// Mutable UI state shared across the extension's pieces (not editor state\n// because it isn't versioned/undoable): the thread we most recently dismissed,\n// so a click that stays inside it doesn't immediately pop it back open.\ntype CommentUIState = { suppressed: AutomergeUrl | null };\n\nconst POPOVER_CLASS = \"cm-comment-popover\";\n\n// A speech-bubble icon + \"Comment\" label, shown floating above the selection.\nconst buildCommentButton = (\n from: number,\n to: number,\n head: number,\n view: EditorView,\n options: CommentUIOptions\n): HTMLElement => {\n const button = document.createElement(\"button\");\n button.className = \"cm-comment-button\";\n button.setAttribute(\"aria-label\", \"Comment\");\n\n button.innerHTML = `\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"\n fill=\"currentColor\" stroke=\"none\"/>\n </svg>\n <span>Comment</span>\n `;\n\n // Without this, the browser focuses the button on mousedown, the editor\n // blurs, the selection collapses, our tooltip source returns null, and\n // CodeMirror tears down the button before `click` fires \u2014 so the first\n // press silently does nothing and the user has to click twice. Swallowing\n // mousedown keeps the editor focused and the selection (and button) alive\n // long enough for the click to land.\n button.addEventListener(\"mousedown\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n });\n\n button.addEventListener(\"click\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n const threadUrl = options.createThreadForRange(from, to);\n if (!threadUrl) return;\n // Open the popover right where the button was (the selection head).\n view.dispatch({ effects: openPopover.of({ threadUrl, anchor: head }) });\n });\n\n return button;\n};\n\n// The popover body: the shared comment-thread tool, rendered inline via the\n// `patchwork-view` custom element. It's mounted inside `view.dom`, so the\n// element's provider events (repo, contact, \u2026) still bubble up to the host.\nconst buildThreadPopover = (\n view: EditorView,\n state: PopoverState,\n options: CommentUIOptions,\n ui: CommentUIState\n): TooltipView => {\n const dom = document.createElement(\"div\");\n dom.className = POPOVER_CLASS;\n\n const threadView = document.createElement(\"patchwork-view\");\n threadView.setAttribute(\"doc-url\", state.threadUrl);\n threadView.setAttribute(\"tool-id\", COMMENT_THREAD_TYPE);\n dom.append(threadView);\n\n const close = () => view.dispatch({ effects: closePopover.of(null) });\n\n // Dismiss on a click anywhere outside the popover, or on Escape. Both are\n // armed on a deferred tick so the very interaction that opened the popover\n // (the button click / the click on commented text) doesn't also close it.\n const onOutsideMouseDown = (event: MouseEvent) => {\n if (!dom.contains(event.target as Node)) close();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") close();\n };\n const doc = view.win.document;\n const armTimer = view.win.setTimeout(() => {\n doc.addEventListener(\"mousedown\", onOutsideMouseDown, true);\n doc.addEventListener(\"keydown\", onKeyDown, true);\n }, 0);\n\n // Close as soon as the draft is submitted (or the thread goes away).\n const stopWatch = options.watchThreadForClose(state.threadUrl, close);\n\n return {\n dom,\n destroy() {\n view.win.clearTimeout(armTimer);\n doc.removeEventListener(\"mousedown\", onOutsideMouseDown, true);\n doc.removeEventListener(\"keydown\", onKeyDown, true);\n stopWatch();\n // Remember this thread as dismissed so clicking within its range again\n // doesn't immediately reopen it (cleared once a click lands elsewhere).\n ui.suppressed = state.threadUrl;\n options.onClose(state.threadUrl);\n },\n };\n};\n\nconst buttonTooltip = (\n state: EditorState,\n options: CommentUIOptions\n): Tooltip | null => {\n const sel = state.selection.main;\n // A read-only editor can't take the comment write, so don't offer the button.\n if (sel.empty || state.readOnly) return null;\n\n // Anchor the button at the drag tail (the selection head) rather than the\n // start, so dragging left-to-right lands it on the right and right-to-left\n // on the left.\n return {\n pos: sel.head,\n above: true,\n strictSide: false,\n arrow: false,\n create: (view) => ({\n dom: buildCommentButton(sel.from, sel.to, sel.head, view, options),\n }),\n };\n};\n\nconst popoverTooltip = (\n state: PopoverState,\n options: CommentUIOptions,\n ui: CommentUIState\n): Tooltip => ({\n pos: state.anchor,\n above: false, // comes out underneath the anchor\n strictSide: false,\n arrow: false,\n create: (view) => buildThreadPopover(view, state, options, ui),\n});\n\ntype FieldValue = {\n popover: PopoverState | null;\n // The floating button is hidden while this is true \u2014 after Escape, or after a\n // comment is added \u2014 until a fresh selection re-arms it.\n buttonDismissed: boolean;\n // A selection is being dragged out with the mouse.\n pointerDown: boolean;\n tooltip: Tooltip | null;\n};\n\n// A single field driving the one tooltip slot: the thread popover when one is\n// open, otherwise the selection's \"Comment\" button.\nconst commentUIField = (options: CommentUIOptions, ui: CommentUIState) =>\n StateField.define<FieldValue>({\n create: (state) => ({\n popover: null,\n buttonDismissed: false,\n pointerDown: false,\n tooltip: buttonTooltip(state, options),\n }),\n update(value, tr) {\n let popover = value.popover;\n let buttonDismissed = value.buttonDismissed;\n let pointerDown = value.pointerDown;\n let popoverChanged = false;\n\n // Read-only can flip mid-session: entering it closes any open popover \u2014\n // its reply box writes to a doc that rejects writes \u2014 and the flip\n // itself must recompute the tooltip so the button hides/re-arms.\n const readOnlyChanged = tr.startState.readOnly !== tr.state.readOnly;\n if (readOnlyChanged && tr.state.readOnly && popover) {\n popover = null;\n popoverChanged = true;\n }\n\n // Keep the popover pinned to its text as the document shifts under it.\n if (popover && tr.docChanged) {\n popover = { ...popover, anchor: tr.changes.mapPos(popover.anchor) };\n popoverChanged = true;\n }\n for (const effect of tr.effects) {\n if (effect.is(openPopover)) {\n popover = effect.value;\n popoverChanged = true;\n } else if (effect.is(closePopover)) {\n popover = null;\n popoverChanged = true;\n // A comment was just added (or dismissed): don't flash the button\n // back up over the still-selected text.\n buttonDismissed = true;\n } else if (effect.is(dismissButton)) {\n buttonDismissed = true;\n } else if (effect.is(setPointerDown)) {\n pointerDown = effect.value;\n }\n }\n // Any fresh selection re-arms the button.\n if (tr.selection) buttonDismissed = false;\n\n if (popover) {\n // Reuse the exact same tooltip object unless the popover state itself\n // changed. Otherwise an unrelated transaction (a stray selection\n // change, an extension reconfigure, \u2026) would hand CodeMirror a fresh\n // Tooltip and it would tear down and remount the thread view mid-edit.\n if (!popoverChanged) {\n return buttonDismissed === value.buttonDismissed &&\n pointerDown === value.pointerDown\n ? value\n : { ...value, buttonDismissed, pointerDown };\n }\n return {\n popover,\n buttonDismissed,\n pointerDown,\n tooltip: popoverTooltip(popover, options, ui),\n };\n }\n\n // No popover: show the selection button, unless it's been dismissed.\n // Recompute only when something relevant changed.\n if (\n !popoverChanged &&\n !tr.docChanged &&\n !tr.selection &&\n !readOnlyChanged &&\n buttonDismissed === value.buttonDismissed &&\n pointerDown === value.pointerDown\n ) {\n return value;\n }\n return {\n popover: null,\n buttonDismissed,\n pointerDown,\n tooltip:\n buttonDismissed || pointerDown\n ? null\n : buttonTooltip(tr.state, options),\n };\n },\n provide: (field) => showTooltip.from(field, (value) => value.tooltip),\n });\n\n// Note the main button going down, and (wherever it's let go \u2014 often outside\n// the editor, at the end of a drag) coming back up.\nexport const trackPointer = (view: EditorView) => {\n view.dispatch({ effects: setPointerDown.of(true) });\n const up = () => {\n view.win.removeEventListener(\"mouseup\", up, true);\n view.win.removeEventListener(\"blur\", up);\n // the editor may have gone while the button was down\n if (view.dom.isConnected)\n view.dispatch({ effects: setPointerDown.of(false) });\n };\n view.win.addEventListener(\"mouseup\", up, true);\n view.win.addEventListener(\"blur\", up);\n};\n\n// Editor-level interactions: click commented text to (re)open its thread, and\n// Escape to dismiss the floating button.\nconst commentInteractions = (\n options: CommentUIOptions,\n ui: CommentUIState,\n field: StateField<FieldValue>\n): Extension =>\n EditorView.domEventHandlers({\n mousedown(event, view) {\n if (event.button === 0) trackPointer(view);\n if (\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.altKey ||\n event.shiftKey\n ) {\n return false;\n }\n // Clicks inside an open popover are the thread tool's own business.\n if ((event.target as HTMLElement | null)?.closest(\".\" + POPOVER_CLASS)) {\n return false;\n }\n // Read-only: don't open threads either \u2014 the popover's reply box would\n // write into a doc that rejects writes.\n if (view.state.readOnly) return false;\n const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n if (pos == null) return false;\n const hit = options.threadAtPos(pos);\n if (!hit) {\n ui.suppressed = null; // clicked away from any comment; re-arm reopening\n return false;\n }\n // Don't reopen the thread we just dismissed while the click stays inside\n // it \u2014 the user has to click elsewhere (or another comment) first.\n if (hit.threadUrl === ui.suppressed) return false;\n ui.suppressed = null;\n view.dispatch({\n effects: openPopover.of({ threadUrl: hit.threadUrl, anchor: hit.from }),\n });\n return false; // let the click also move the cursor as usual\n },\n keydown(event, view) {\n if (event.key !== \"Escape\") return false;\n const state = view.state.field(field, false);\n // While a popover is open its own Escape handler wins (it also works when\n // the popover's textarea holds focus); here we only hide the button.\n if (!state || state.popover || !state.tooltip) return false;\n view.dispatch({ effects: dismissButton.of(null) });\n return true;\n },\n });\n\n// Global colours are lifted into local --cm-comment-* tokens in a\n// :root/:host/[theme] block so they re-evaluate when the theme swaps. This\n// derivation can't live in the baseTheme below \u2014 CodeMirror prefixes every\n// non-\"&\" selector with the editor's scope class, so a `:root` key would\n// become `.cm... :root` and never match. It's injected as a plain stylesheet\n// instead, and the button reads only the derived tokens. The button is editor\n// UI, so it derives from --editor-* (not --studio-*, which is studio chrome).\nconst COMMENT_BUTTON_VARS_ID = \"cm-comment-button-vars\";\nconst ensureCommentButtonVars = (): void => {\n if (\n typeof document === \"undefined\" ||\n document.getElementById(COMMENT_BUTTON_VARS_ID)\n ) {\n return;\n }\n const style = document.createElement(\"style\");\n style.id = COMMENT_BUTTON_VARS_ID;\n style.textContent = `\n :root, :host, [theme] {\n /* the button sits on the editor surface: bg is the plain editor fill,\n text is the editor ink, and hover lifts a gentle one step up the fill\n ramp. see --text-editor-* in the theme. */\n --cm-comment-button-fg: var(--text-editor-line, #0a2b23);\n --cm-comment-button-bg: var(--text-editor-fill, #35f7ca);\n --cm-comment-button-bg-hover: var(--text-editor-fill-offset-10, #2ce0b6);\n }\n `;\n document.head.append(style);\n};\n\nconst commentUITheme = EditorView.baseTheme({\n \".cm-tooltip:has(.cm-comment-button)\": {\n border: \"none\",\n background: \"transparent\",\n boxShadow: \"none\",\n },\n \".cm-comment-button\": {\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"5px\",\n padding: \"4px 9px\",\n font: \"inherit\",\n fontSize: \"12px\",\n fontWeight: \"500\",\n lineHeight: \"1\",\n color: \"var(--cm-comment-button-fg)\",\n background: \"var(--cm-comment-button-bg)\",\n border: \"none\",\n borderRadius: \"6px\",\n boxShadow: \"0 2px 8px rgba(0, 0, 0, 0.25)\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n userSelect: \"none\",\n whiteSpace: \"nowrap\",\n transition: \"background-color max(1ms, calc(0.15s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))\",\n // fades in once the selection's made (it waits for the mouse to come up)\n animation:\n \"cm-comment-button-in max(1ms, calc(0.16s * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1))) ease-out both\",\n },\n \"@keyframes cm-comment-button-in\": {\n from: { opacity: \"0\", transform: \"translateY(2px)\" },\n to: { opacity: \"1\", transform: \"none\" },\n },\n \".cm-comment-button:hover\": {\n background: \"var(--cm-comment-button-bg-hover)\",\n },\n \".cm-comment-button svg\": {\n display: \"block\",\n },\n // The thread tool carries its own card (background, border, padding), so the\n // tooltip wrapper is just a transparent, softly-lifted host for it.\n \".cm-tooltip:has(.cm-comment-popover)\": {\n border: \"none\",\n background: \"transparent\",\n boxShadow: \"none\",\n },\n \".cm-comment-popover\": {\n width: \"min(360px, 92vw)\",\n marginTop: \"6px\",\n fontFamily: \"var(--editor-family)\",\n filter: \"drop-shadow(0 4px 14px rgba(0, 0, 0, 0.25))\",\n },\n // The popover supplies its own frame (the tooltip wrapper's shadow), so the\n // thread card inside drops its own border and shadow to avoid doubling up.\n \".cm-comment-popover .comments-thread-card\": {\n border: \"0\",\n boxShadow: \"none\",\n },\n // The draft textarea is width:100% with its own padding + border; without\n // border-box it spills out of the card's right edge (the card's own reset is\n // scoped to the sidebar panel, which the popover isn't inside).\n \".cm-comment-popover .comment-draft-editor\": {\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n minWidth: \"0\",\n width: \"100%\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-content\": {\n minHeight: \"0\",\n minWidth: \"0\",\n margin: \"0\",\n padding: \"8px\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-line\": {\n margin: \"0\",\n padding: \"0\",\n },\n \".cm-comment-popover .comment-draft-editor .cm-scroller\": {\n minWidth: \"0\",\n },\n});\n\n// The comment UI: the floating \"Comment\" button on a selection, and the thread\n// popover it (and clicking commented text) opens.\nexport const commentUI = (options: CommentUIOptions): Extension => {\n ensureCommentButtonVars();\n const ui: CommentUIState = { suppressed: null };\n const field = commentUIField(options, ui);\n return [field, commentInteractions(options, ui, field), commentUITheme];\n};\n", "import {\n cursor,\n type AutomergeUrl,\n type DocHandle,\n} from \"@automerge/automerge-repo/slim\";\n\nexport type DocWithComments = {\n \"@comments\"?: {\n threads: CommentThread[];\n };\n};\n\n/**\n * The `@patchwork.type` stamped on a comment thread so a datatype (and thus a\n * relevant tool) can be resolved when the thread's subdocument is opened on its\n * own. Mirrors `COMMENT_THREAD_TYPE` in the comments-view module.\n */\nexport const COMMENT_THREAD_TYPE = \"comment-thread\";\n\nexport type CommentThread = {\n id: string;\n refs: AutomergeUrl[];\n isResolved: boolean;\n comments: Comment[];\n \"@patchwork\"?: { type: string; title?: string };\n};\n\nexport type Comment = {\n id: string;\n content?: string;\n draftContent?: string;\n contactUrl: AutomergeUrl;\n timestamp: number;\n};\n\n// Creates a thread (with one empty, unfilled comment) targeting [from, to] and\n// returns the thread's url so the caller can open it \u2014 e.g. in a popover \u2014 for\n// the author to fill in. An abandoned, never-filled comment should be cleaned\n// up by the caller (see the comment UI's `onClose`).\nexport function createCommentForRange(\n handle: DocHandle<unknown>,\n path: readonly string[],\n from: number,\n to: number,\n contactUrl: AutomergeUrl\n): AutomergeUrl {\n const targetUrl = handle.sub(...path, cursor(from, to)).url;\n const threadId = crypto.randomUUID();\n const commentId = crypto.randomUUID();\n\n handle.change((doc: DocWithComments) => {\n doc[\"@comments\"] ??= { threads: [] };\n doc[\"@comments\"].threads.push({\n id: threadId,\n refs: [targetUrl],\n isResolved: false,\n comments: [\n {\n id: commentId,\n contactUrl,\n timestamp: Date.now(),\n },\n ],\n \"@patchwork\": { type: COMMENT_THREAD_TYPE },\n });\n });\n\n return handle.sub(\"@comments\", \"threads\", { id: threadId }).url;\n}\n", "import { createEffect, createSignal, onCleanup, onMount, } from \"solid-js\";\nimport { createStore, reconcile } from \"solid-js/store\";\nimport * as Providers from \"@inkandswitch/patchwork-providers\";\nfunction resolveElement(source) {\n return typeof source === \"function\" ? source() : source;\n}\n/**\n * Generic reactive request. Resolves the first value a provider emits for\n * `selector` (via the one-shot `request` helper) and returns an accessor that\n * reads `undefined` until then. `T` is the response type.\n */\nexport function request(element, selector) {\n const [value, setValue] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n Providers.request(target, selector).then((v) => {\n if (v == null)\n return;\n setValue(() => v);\n });\n });\n return value;\n}\nexport function subscribe(element, selector, initialValue) {\n const [value, setValue] = createSignal(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, setValue);\n onCleanup(unsubscribe);\n });\n return value;\n}\n/**\n * Store-backed subscription. Use this when a consumer wants Solid's\n * fine-grained nested reactivity for incoming JSON object or array snapshots.\n * Requires an initial object/array so the store has a stable root.\n *\n * `reconcile` preserves stable object/array identity where possible, so this\n * helper is not a good fit when the top-level value is used as an identity key\n * for resources or memos.\n */\nexport function subscribeReconciled(element, selector, initialValue) {\n const [store, setStore] = createStore(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, (v) => {\n setStore(reconcile(v));\n });\n onCleanup(unsubscribe);\n });\n return store;\n}\n/**\n * Handle-specialized subscription. Use when the answering provider emits an\n * `AutomergeUrl`. The handle is recovered locally from the global repo\n * (`window.repo`), so it stays fully live \u2014 reads project reactively and writes go\n * straight back to the same repo. Returns `[doc, handle]` matching the shape\n * of `solid-automerge`'s `useDocument`; both read `undefined` until the first\n * url arrives. `T` is the doc shape inside the handle.\n */\nexport function subscribeDoc(element, selector) {\n const [handle, setHandle] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n let canceled = false;\n const unsubscribe = Providers.subscribe(target, selector, (url) => {\n if (!url)\n return;\n const repo = \"repo\" in window ? window.repo : undefined;\n if (!repo)\n return;\n void Promise.resolve(repo.find(url)).then((h) => {\n if (canceled)\n return;\n setHandle(() => h);\n });\n });\n onCleanup(() => {\n canceled = true;\n unsubscribe();\n });\n });\n // Mirror the doc into a store by `reconcile`-ing against the *materialized*\n // snapshot (`handle.doc()`) on every change, rather than replaying the\n // change's incremental patches (as `solid-automerge`'s `createDocumentProjection`\n // / `autoproduce` does).\n //\n // A whole-value write such as `doc.list = [...]` is lowered by Automerge to\n // `putObjectFromHydrate`, whose patch stream is a `put` that materializes the\n // new container *plus* per-element `insert` patches. Replaying that delta with\n // `applyPatches` double-applies the contents \u2014 the `put` seeds `[a, b]`, then\n // the inserts splice `a, b` in again, yielding `[a, b, a, b]` \u2014 even though the\n // document itself is correct. The materialized snapshot is always\n // authoritative, so reconciling against it is robust to any valid change,\n // whole-array reassignment included.\n const [store, setStore] = createStore({});\n const [ready, setReady] = createSignal(false);\n createEffect(() => {\n const h = handle();\n if (!h) {\n setReady(false);\n return;\n }\n const sync = () => {\n setStore(reconcile((h.doc() ?? {})));\n setReady(true);\n };\n sync();\n h.on(\"change\", sync);\n onCleanup(() => h.off(\"change\", sync));\n });\n const doc = () => (ready() ? store : undefined);\n return [doc, handle];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["cursor", "subscribe", "subscribe", "cursor"]
7
7
  }
@@ -14,17 +14,21 @@ import {
14
14
  // src/themes/embed.ts
15
15
  import { EditorView } from "@codemirror/view";
16
16
  var embedTheme = EditorView.baseTheme({
17
+ // The frame's border only shows once you've clicked into the embed; until
18
+ // then it sits in the text like the rest of the document.
17
19
  ".cm-embed": {
18
20
  display: "block",
19
- border: "var(--studio-border-width, 1px) var(--studio-border-style, solid)",
21
+ border: "var(--studio-border-width, 1px) var(--studio-border-style, solid) transparent",
20
22
  borderRadius: "4px",
21
- overflow: "hidden"
23
+ overflow: "hidden",
24
+ outline: "none",
25
+ transition: "border-color max(1ms, calc(150ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))"
22
26
  },
23
- "&light .cm-embed": {
24
- borderColor: "#ddd"
27
+ "&light .cm-embed:focus-within": {
28
+ borderColor: "var(--text-editor-fill-offset-30, #ddd)"
25
29
  },
26
- "&dark .cm-embed": {
27
- borderColor: "#333"
30
+ "&dark .cm-embed:focus-within": {
31
+ borderColor: "var(--text-editor-fill-offset-30, #333)"
28
32
  },
29
33
  // Images are just images: no border, natural size, never wider than the text.
30
34
  "&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image": {
@@ -61,6 +65,7 @@ var EmbedWidget = class extends WidgetType {
61
65
  toDOM(view) {
62
66
  const container = document.createElement("div");
63
67
  container.className = "cm-embed";
68
+ container.tabIndex = -1;
64
69
  if (this.toolId) {
65
70
  this.mountFrame(view, container);
66
71
  } else {
@@ -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 * 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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",
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 // focusable by a click anywhere in it (not by Tab), so :focus-within \u2014\n // which shows its frame \u2014 holds even when what's clicked can't take focus.\n container.tabIndex = -1;\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 // The frame's border only shows once you've clicked into the embed; until\n // then it sits in the text like the rest of the document.\n \".cm-embed\": {\n display: \"block\",\n border: \"var(--studio-border-width, 1px) var(--studio-border-style, solid) transparent\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n outline: \"none\",\n transition:\n \"border-color max(1ms, calc(150ms * var(--studio-motion-scale, 1) * var(--studio-motion-preference, 1)))\",\n },\n \"&light .cm-embed:focus-within\": {\n borderColor: \"var(--text-editor-fill-offset-30, #ddd)\",\n },\n \"&dark .cm-embed:focus-within\": {\n borderColor: \"var(--text-editor-fill-offset-30, #333)\",\n },\n // Images are just images: no border, natural size, never wider than the text.\n \"&light .cm-embed.cm-embed-image, &dark .cm-embed.cm-embed-image\": {\n border: \"none\",\n borderRadius: \"0\",\n },\n \".cm-embed-image > img\": {\n display: \"block\",\n maxWidth: \"100%\",\n height: \"auto\",\n },\n \".cm-embed > patchwork-view\": {\n display: \"block\",\n height: \"500px\",\n width: \"100%\",\n },\n});\n"],
5
+ "mappings": "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",
6
6
  "names": ["EditorView", "seen", "EditorView", "refs"]
7
7
  }
@@ -1 +1 @@
1
- automerge:tC7RM5UgFfcwqLSrpZF2AfMsAV4
1
+ automerge:3tsrRFEVCRpyL4ZAVWhajnAukvmw