@inkandswitch/patchwork-pkg-base 0.0.126 → 0.0.127

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:3LxJsuExcL8S2E8j8758gWkK1Hdp
1
+ automerge:2nY33XxqmEjeUiRH68vhpL8W9FrT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.126",
3
+ "version": "0.0.127",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -86,6 +86,22 @@ function getLinks(view) {
86
86
  from,
87
87
  to,
88
88
  enter: (node) => {
89
+ if (node.name === "Autolink") {
90
+ const url = node.node.getChild("URL");
91
+ const inside = selection.from >= node.from && selection.from <= node.to;
92
+ const spans = selection.from < node.from && selection.to > node.to;
93
+ if (url && !inside && !spans) {
94
+ const text = state.doc.sliceString(url.from, url.to);
95
+ const href = /^[a-z][a-z0-9+.-]*:/i.test(text) || !text.includes("@") ? text : `mailto:${text}`;
96
+ widgets.push(
97
+ Decoration.replace({ widget: new LinkWidget(href, text) }).range(
98
+ node.from,
99
+ node.to
100
+ )
101
+ );
102
+ }
103
+ return;
104
+ }
89
105
  if (node.name === "Link") {
90
106
  const linkFrom = node.from;
91
107
  const linkTo = node.to;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/extensions/links.ts", "../../src/themes/links.ts"],
4
- "sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport {\n type EditorState,\n Range,\n StateEffect,\n StateField,\n} from \"@codemirror/state\";\nimport { syntaxTree } from \"@codemirror/language\";\nimport { isValidAutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { linkTheme } from \"../themes/links.ts\";\n\n/**\n * Open a link from the editor. Automerge urls open inside Patchwork rather\n * than as a browser URL (automerge: isn't navigable); bubbles/composed so the\n * event escapes the editor and any shadow root up to whoever drives selection.\n */\nexport function openUrl(url: string, from: Element) {\n if (isValidAutomergeUrl(url)) {\n from.dispatchEvent(\n new CustomEvent(\"patchwork:open-document\", {\n detail: { url },\n bubbles: true,\n composed: true,\n }),\n );\n } else {\n // GFM autolinks `www.example.com` with no scheme.\n const href = /^[a-z][a-z0-9+.-]*:/i.test(url) ? url : `https://${url}`;\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n }\n}\n\n/**\n * Widget to render a clickable link in the editor\n */\nclass LinkWidget extends WidgetType {\n readonly url: string;\n readonly text: string;\n\n constructor(url: string, text: string) {\n super();\n this.url = url;\n this.text = text;\n }\n\n eq(other: LinkWidget) {\n return other.url === this.url && other.text === this.text;\n }\n\n toDOM() {\n const link = document.createElement(\"a\");\n link.href = this.url;\n link.textContent = this.text;\n link.className = \"cm-link\";\n link.title = this.url;\n\n link.onclick = (e) => {\n e.preventDefault();\n openUrl(this.url, link);\n };\n\n return link;\n }\n\n ignoreEvent(e: Event) {\n // Let click events through to our widget\n return e.type !== \"click\";\n }\n}\n\nfunction getLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n const selection = state.selection.main;\n\n for (let { from, to } of view.visibleRanges) {\n syntaxTree(state).iterate({\n from,\n to,\n enter: (node) => {\n // Match markdown link syntax\n if (node.name === \"Link\") {\n const linkFrom = node.from;\n const linkTo = node.to;\n\n // Check if cursor is inside this link\n const cursorInLink =\n selection.from >= linkFrom && selection.from <= linkTo;\n // Check if the link is inside the selection\n const selectionSpansLink =\n selection.from < linkFrom && selection.to > linkTo;\n\n // Replace if cursor is outside the link and there's no selection across the link\n if (!cursorInLink && !selectionSpansLink) {\n const linkText = state.doc.sliceString(linkFrom, linkTo);\n const match = linkText.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n\n if (match) {\n const [, text, url] = match;\n\n // Replace the entire [text](url) with just the link widget\n const deco = Decoration.replace({\n widget: new LinkWidget(url, text),\n });\n\n widgets.push(deco.range(linkFrom, linkTo));\n }\n }\n }\n },\n });\n }\n\n return Decoration.set(widgets);\n}\n\nconst linkPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Recompute when doc changes, selection moves, or viewport changes\n if (\n update.docChanged ||\n update.selectionSet ||\n update.viewportChanged ||\n syntaxTree(update.startState) !== syntaxTree(update.state)\n ) {\n this.decorations = getLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n }\n);\n\ntype SyntaxNode = ReturnType<ReturnType<typeof syntaxTree>[\"resolveInner\"]>;\n\n/**\n * The link at `pos`, if any: an inline `[text](url)`, an autolink `<url>`, a\n * GFM bare url, or the url in a `[ref]: url` definition. `from`/`to` cover\n * the whole link as written. Reference-style `[text][ref]` links have no url\n * of their own and are skipped.\n */\nexport function linkAt(\n state: EditorState,\n pos: number\n): { from: number; to: number; url: string } | null {\n const slice = (n: SyntaxNode) => state.doc.sliceString(n.from, n.to);\n for (const side of [1, -1] as const) {\n for (\n let node: SyntaxNode | null = syntaxTree(state).resolveInner(pos, side);\n node;\n node = node.parent\n ) {\n const inLink = node.parent?.name === \"Link\" || node.parent?.name === \"Autolink\";\n if (node.name === \"URL\" && !inLink)\n return { from: node.from, to: node.to, url: slice(node) };\n if (node.name === \"Link\" || node.name === \"Autolink\") {\n const url = node.getChild(\"URL\");\n return url ? { from: node.from, to: node.to, url: slice(url) } : null;\n }\n }\n }\n return null;\n}\n\nexport const urlAt = (state: EditorState, pos: number) =>\n linkAt(state, pos)?.url ?? null;\n\n/**\n * The link actually under the pointer. `posAndSideAtCoords` snaps to the\n * nearest position, so without the distance check the blank space past the\n * end of a line would count as hovering a link that ends the line.\n */\nfunction linkUnderPointer(view: EditorView, x: number, y: number) {\n const hit = view.posAndSideAtCoords({ x, y });\n if (!hit) return null;\n const at = view.coordsAtPos(hit.pos, hit.assoc);\n if (\n !at ||\n y < at.top ||\n y > at.bottom ||\n Math.abs(x - at.left) > view.defaultCharacterWidth * 1.5\n )\n return null;\n return linkAt(view.state, hit.pos);\n}\n\nconst isMac =\n typeof navigator !== \"undefined\" &&\n /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);\nconst modHeld = (e: KeyboardEvent | MouseEvent) =>\n isMac ? e.metaKey : e.ctrlKey;\n\n/** The one link to show as clickable: under the pointer, with Mod held. */\nconst setHover = StateEffect.define<{ from: number; to: number } | null>();\nconst hoverMark = Decoration.mark({ class: \"cm-md-link-hover\" });\n\nconst hoverField = StateField.define<DecorationSet>({\n create: () => Decoration.none,\n update(deco, tr) {\n deco = deco.map(tr.changes);\n for (const e of tr.effects) {\n if (e.is(setHover)) {\n deco = e.value\n ? Decoration.set(hoverMark.range(e.value.from, e.value.to))\n : Decoration.none;\n }\n }\n return deco;\n },\n provide: (f) => EditorView.decorations.from(f),\n});\n\n/**\n * Cmd-click (Ctrl off a Mac) opens the link under the pointer, even while the\n * cursor is inside it and it's showing as raw markdown. While the key is held\n * the hovered link gets `cm-md-link-hover`, so the theme can underline it.\n */\nconst modClick = ViewPlugin.fromClass(\n class {\n pointer: { x: number; y: number } | null = null;\n mod = false;\n\n constructor(readonly view: EditorView) {}\n\n refresh() {\n const { view } = this;\n const link =\n this.mod && this.pointer\n ? linkUnderPointer(view, this.pointer.x, this.pointer.y)\n : null;\n let current: { from: number; to: number } | null = null;\n view.state.field(hoverField).between(0, view.state.doc.length, (from, to) => {\n current = { from, to };\n });\n const same = (a: typeof current, b: typeof current) =>\n a?.from === b?.from && a?.to === b?.to;\n const next = link && { from: link.from, to: link.to };\n if (!same(current, next)) view.dispatch({ effects: setHover.of(next) });\n }\n },\n {\n eventHandlers: {\n mousedown(event, view) {\n if (event.button !== 0 || !modHeld(event)) return false;\n const link = linkUnderPointer(view, event.clientX, event.clientY);\n if (!link) return false;\n event.preventDefault();\n openUrl(link.url, view.dom);\n return true;\n },\n mousemove(event) {\n this.pointer = { x: event.clientX, y: event.clientY };\n this.mod = modHeld(event);\n this.refresh();\n },\n mouseleave() {\n this.pointer = null;\n this.refresh();\n },\n keydown(event) {\n this.mod = modHeld(event);\n this.refresh();\n },\n keyup(event) {\n this.mod = modHeld(event);\n this.refresh();\n },\n blur() {\n this.mod = false;\n this.refresh();\n },\n },\n }\n);\n\nexport function markdownLinks() {\n return [linkPlugin, hoverField, modClick, linkTheme];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\nexport const linkTheme = EditorView.baseTheme({\n \".cm-link\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n // the widget replaces the link's text, so the theme's `t.link` highlight\n // (theming/src/codemirror-theme.ts) never reaches it: read the same tokens.\n fontFamily: \"var(--markdown-body-family, var(--text-editor-family-sans))\",\n color: \"var(--syntax-link, #0066cc)\",\n backgroundColor: \"var(--syntax-fill-link)\",\n fontStyle: \"var(--syntax-style-link)\",\n fontWeight: \"var(--syntax-weight-link)\",\n \"&:hover\": {\n color: \"inherit\",\n },\n },\n \".cm-md-link-hover, .cm-md-link-hover *\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n },\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n EditorView,\n Decoration,\n WidgetType,\n ViewPlugin,\n ViewUpdate,\n type DecorationSet,\n} from \"@codemirror/view\";\nimport {\n type EditorState,\n Range,\n StateEffect,\n StateField,\n} from \"@codemirror/state\";\nimport { syntaxTree } from \"@codemirror/language\";\nimport { isValidAutomergeUrl } from \"@automerge/automerge-repo/slim\";\nimport { linkTheme } from \"../themes/links.ts\";\n\n/**\n * Open a link from the editor. Automerge urls open inside Patchwork rather\n * than as a browser URL (automerge: isn't navigable); bubbles/composed so the\n * event escapes the editor and any shadow root up to whoever drives selection.\n */\nexport function openUrl(url: string, from: Element) {\n if (isValidAutomergeUrl(url)) {\n from.dispatchEvent(\n new CustomEvent(\"patchwork:open-document\", {\n detail: { url },\n bubbles: true,\n composed: true,\n }),\n );\n } else {\n // GFM autolinks `www.example.com` with no scheme.\n const href = /^[a-z][a-z0-9+.-]*:/i.test(url) ? url : `https://${url}`;\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n }\n}\n\n/**\n * Widget to render a clickable link in the editor\n */\nclass LinkWidget extends WidgetType {\n readonly url: string;\n readonly text: string;\n\n constructor(url: string, text: string) {\n super();\n this.url = url;\n this.text = text;\n }\n\n eq(other: LinkWidget) {\n return other.url === this.url && other.text === this.text;\n }\n\n toDOM() {\n const link = document.createElement(\"a\");\n link.href = this.url;\n link.textContent = this.text;\n link.className = \"cm-link\";\n link.title = this.url;\n\n link.onclick = (e) => {\n e.preventDefault();\n openUrl(this.url, link);\n };\n\n return link;\n }\n\n ignoreEvent(e: Event) {\n // Let click events through to our widget\n return e.type !== \"click\";\n }\n}\n\nfunction getLinks(view: EditorView) {\n const widgets: Range<Decoration>[] = [];\n const { state } = view;\n const selection = state.selection.main;\n\n for (let { from, to } of view.visibleRanges) {\n syntaxTree(state).iterate({\n from,\n to,\n enter: (node) => {\n // `<https://\u2026>`, `<fax:\u2026>`, `<a@b.c>`: the url, without its brackets\n if (node.name === \"Autolink\") {\n const url = node.node.getChild(\"URL\");\n const inside =\n selection.from >= node.from && selection.from <= node.to;\n const spans = selection.from < node.from && selection.to > node.to;\n if (url && !inside && !spans) {\n const text = state.doc.sliceString(url.from, url.to);\n const href =\n /^[a-z][a-z0-9+.-]*:/i.test(text) || !text.includes(\"@\")\n ? text\n : `mailto:${text}`;\n widgets.push(\n Decoration.replace({ widget: new LinkWidget(href, text) }).range(\n node.from,\n node.to\n )\n );\n }\n return;\n }\n\n // Match markdown link syntax\n if (node.name === \"Link\") {\n const linkFrom = node.from;\n const linkTo = node.to;\n\n // Check if cursor is inside this link\n const cursorInLink =\n selection.from >= linkFrom && selection.from <= linkTo;\n // Check if the link is inside the selection\n const selectionSpansLink =\n selection.from < linkFrom && selection.to > linkTo;\n\n // Replace if cursor is outside the link and there's no selection across the link\n if (!cursorInLink && !selectionSpansLink) {\n const linkText = state.doc.sliceString(linkFrom, linkTo);\n const match = linkText.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n\n if (match) {\n const [, text, url] = match;\n\n // Replace the entire [text](url) with just the link widget\n const deco = Decoration.replace({\n widget: new LinkWidget(url, text),\n });\n\n widgets.push(deco.range(linkFrom, linkTo));\n }\n }\n }\n },\n });\n }\n\n return Decoration.set(widgets);\n}\n\nconst linkPlugin = ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n\n constructor(view: EditorView) {\n this.decorations = getLinks(view);\n }\n\n update(update: ViewUpdate) {\n // Recompute when doc changes, selection moves, or viewport changes\n if (\n update.docChanged ||\n update.selectionSet ||\n update.viewportChanged ||\n syntaxTree(update.startState) !== syntaxTree(update.state)\n ) {\n this.decorations = getLinks(update.view);\n }\n }\n },\n {\n decorations: (v) => v.decorations,\n }\n);\n\ntype SyntaxNode = ReturnType<ReturnType<typeof syntaxTree>[\"resolveInner\"]>;\n\n/**\n * The link at `pos`, if any: an inline `[text](url)`, an autolink `<url>`, a\n * GFM bare url, or the url in a `[ref]: url` definition. `from`/`to` cover\n * the whole link as written. Reference-style `[text][ref]` links have no url\n * of their own and are skipped.\n */\nexport function linkAt(\n state: EditorState,\n pos: number\n): { from: number; to: number; url: string } | null {\n const slice = (n: SyntaxNode) => state.doc.sliceString(n.from, n.to);\n for (const side of [1, -1] as const) {\n for (\n let node: SyntaxNode | null = syntaxTree(state).resolveInner(pos, side);\n node;\n node = node.parent\n ) {\n const inLink = node.parent?.name === \"Link\" || node.parent?.name === \"Autolink\";\n if (node.name === \"URL\" && !inLink)\n return { from: node.from, to: node.to, url: slice(node) };\n if (node.name === \"Link\" || node.name === \"Autolink\") {\n const url = node.getChild(\"URL\");\n return url ? { from: node.from, to: node.to, url: slice(url) } : null;\n }\n }\n }\n return null;\n}\n\nexport const urlAt = (state: EditorState, pos: number) =>\n linkAt(state, pos)?.url ?? null;\n\n/**\n * The link actually under the pointer. `posAndSideAtCoords` snaps to the\n * nearest position, so without the distance check the blank space past the\n * end of a line would count as hovering a link that ends the line.\n */\nfunction linkUnderPointer(view: EditorView, x: number, y: number) {\n const hit = view.posAndSideAtCoords({ x, y });\n if (!hit) return null;\n const at = view.coordsAtPos(hit.pos, hit.assoc);\n if (\n !at ||\n y < at.top ||\n y > at.bottom ||\n Math.abs(x - at.left) > view.defaultCharacterWidth * 1.5\n )\n return null;\n return linkAt(view.state, hit.pos);\n}\n\nconst isMac =\n typeof navigator !== \"undefined\" &&\n /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);\nconst modHeld = (e: KeyboardEvent | MouseEvent) =>\n isMac ? e.metaKey : e.ctrlKey;\n\n/** The one link to show as clickable: under the pointer, with Mod held. */\nconst setHover = StateEffect.define<{ from: number; to: number } | null>();\nconst hoverMark = Decoration.mark({ class: \"cm-md-link-hover\" });\n\nconst hoverField = StateField.define<DecorationSet>({\n create: () => Decoration.none,\n update(deco, tr) {\n deco = deco.map(tr.changes);\n for (const e of tr.effects) {\n if (e.is(setHover)) {\n deco = e.value\n ? Decoration.set(hoverMark.range(e.value.from, e.value.to))\n : Decoration.none;\n }\n }\n return deco;\n },\n provide: (f) => EditorView.decorations.from(f),\n});\n\n/**\n * Cmd-click (Ctrl off a Mac) opens the link under the pointer, even while the\n * cursor is inside it and it's showing as raw markdown. While the key is held\n * the hovered link gets `cm-md-link-hover`, so the theme can underline it.\n */\nconst modClick = ViewPlugin.fromClass(\n class {\n pointer: { x: number; y: number } | null = null;\n mod = false;\n\n constructor(readonly view: EditorView) {}\n\n refresh() {\n const { view } = this;\n const link =\n this.mod && this.pointer\n ? linkUnderPointer(view, this.pointer.x, this.pointer.y)\n : null;\n let current: { from: number; to: number } | null = null;\n view.state.field(hoverField).between(0, view.state.doc.length, (from, to) => {\n current = { from, to };\n });\n const same = (a: typeof current, b: typeof current) =>\n a?.from === b?.from && a?.to === b?.to;\n const next = link && { from: link.from, to: link.to };\n if (!same(current, next)) view.dispatch({ effects: setHover.of(next) });\n }\n },\n {\n eventHandlers: {\n mousedown(event, view) {\n if (event.button !== 0 || !modHeld(event)) return false;\n const link = linkUnderPointer(view, event.clientX, event.clientY);\n if (!link) return false;\n event.preventDefault();\n openUrl(link.url, view.dom);\n return true;\n },\n mousemove(event) {\n this.pointer = { x: event.clientX, y: event.clientY };\n this.mod = modHeld(event);\n this.refresh();\n },\n mouseleave() {\n this.pointer = null;\n this.refresh();\n },\n keydown(event) {\n this.mod = modHeld(event);\n this.refresh();\n },\n keyup(event) {\n this.mod = modHeld(event);\n this.refresh();\n },\n blur() {\n this.mod = false;\n this.refresh();\n },\n },\n }\n);\n\nexport function markdownLinks() {\n return [linkPlugin, hoverField, modClick, linkTheme];\n}\n", "import { EditorView } from \"@codemirror/view\";\n\nexport const linkTheme = EditorView.baseTheme({\n \".cm-link\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n // the widget replaces the link's text, so the theme's `t.link` highlight\n // (theming/src/codemirror-theme.ts) never reaches it: read the same tokens.\n fontFamily: \"var(--markdown-body-family, var(--text-editor-family-sans))\",\n color: \"var(--syntax-link, #0066cc)\",\n backgroundColor: \"var(--syntax-fill-link)\",\n fontStyle: \"var(--syntax-style-link)\",\n fontWeight: \"var(--syntax-weight-link)\",\n \"&:hover\": {\n color: \"inherit\",\n },\n },\n \".cm-md-link-hover, .cm-md-link-hover *\": {\n textDecoration: \"underline\",\n cursor: \"var(--studio-cursor-pointer, pointer)\",\n },\n});\n"],
5
+ "mappings": ";AAAA;AAAA,EACE,cAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EAGE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;;;ACfpC,SAAS,kBAAkB;AAEpB,IAAM,YAAY,WAAW,UAAU;AAAA,EAC5C,YAAY;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA;AAAA;AAAA,IAGR,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,iBAAiB;AAAA,IACjB,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,WAAW;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EACA,0CAA0C;AAAA,IACxC,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EACV;AACF,CAAC;;;ADEM,SAAS,QAAQ,KAAa,MAAe;AAClD,MAAI,oBAAoB,GAAG,GAAG;AAC5B,SAAK;AAAA,MACH,IAAI,YAAY,2BAA2B;AAAA,QACzC,QAAQ,EAAE,IAAI;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,OAAO;AAEL,UAAM,OAAO,uBAAuB,KAAK,GAAG,IAAI,MAAM,WAAW,GAAG;AACpE,WAAO,KAAK,MAAM,UAAU,qBAAqB;AAAA,EACnD;AACF;AAKA,IAAM,aAAN,cAAyB,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,KAAa,MAAc;AACrC,UAAM;AACN,SAAK,MAAM;AACX,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,GAAG,OAAmB;AACpB,WAAO,MAAM,QAAQ,KAAK,OAAO,MAAM,SAAS,KAAK;AAAA,EACvD;AAAA,EAEA,QAAQ;AACN,UAAM,OAAO,SAAS,cAAc,GAAG;AACvC,SAAK,OAAO,KAAK;AACjB,SAAK,cAAc,KAAK;AACxB,SAAK,YAAY;AACjB,SAAK,QAAQ,KAAK;AAElB,SAAK,UAAU,CAAC,MAAM;AACpB,QAAE,eAAe;AACjB,cAAQ,KAAK,KAAK,IAAI;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,GAAU;AAEpB,WAAO,EAAE,SAAS;AAAA,EACpB;AACF;AAEA,SAAS,SAAS,MAAkB;AAClC,QAAM,UAA+B,CAAC;AACtC,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,YAAY,MAAM,UAAU;AAElC,WAAS,EAAE,MAAM,GAAG,KAAK,KAAK,eAAe;AAC3C,eAAW,KAAK,EAAE,QAAQ;AAAA,MACxB;AAAA,MACA;AAAA,MACA,OAAO,CAAC,SAAS;AAEf,YAAI,KAAK,SAAS,YAAY;AAC5B,gBAAM,MAAM,KAAK,KAAK,SAAS,KAAK;AACpC,gBAAM,SACJ,UAAU,QAAQ,KAAK,QAAQ,UAAU,QAAQ,KAAK;AACxD,gBAAM,QAAQ,UAAU,OAAO,KAAK,QAAQ,UAAU,KAAK,KAAK;AAChE,cAAI,OAAO,CAAC,UAAU,CAAC,OAAO;AAC5B,kBAAM,OAAO,MAAM,IAAI,YAAY,IAAI,MAAM,IAAI,EAAE;AACnD,kBAAM,OACJ,uBAAuB,KAAK,IAAI,KAAK,CAAC,KAAK,SAAS,GAAG,IACnD,OACA,UAAU,IAAI;AACpB,oBAAQ;AAAA,cACN,WAAW,QAAQ,EAAE,QAAQ,IAAI,WAAW,MAAM,IAAI,EAAE,CAAC,EAAE;AAAA,gBACzD,KAAK;AAAA,gBACL,KAAK;AAAA,cACP;AAAA,YACF;AAAA,UACF;AACA;AAAA,QACF;AAGA,YAAI,KAAK,SAAS,QAAQ;AACxB,gBAAM,WAAW,KAAK;AACtB,gBAAM,SAAS,KAAK;AAGpB,gBAAM,eACJ,UAAU,QAAQ,YAAY,UAAU,QAAQ;AAElD,gBAAM,qBACJ,UAAU,OAAO,YAAY,UAAU,KAAK;AAG9C,cAAI,CAAC,gBAAgB,CAAC,oBAAoB;AACxC,kBAAM,WAAW,MAAM,IAAI,YAAY,UAAU,MAAM;AACvD,kBAAM,QAAQ,SAAS,MAAM,yBAAyB;AAEtD,gBAAI,OAAO;AACT,oBAAM,CAAC,EAAE,MAAM,GAAG,IAAI;AAGtB,oBAAM,OAAO,WAAW,QAAQ;AAAA,gBAC9B,QAAQ,IAAI,WAAW,KAAK,IAAI;AAAA,cAClC,CAAC;AAED,sBAAQ,KAAK,KAAK,MAAM,UAAU,MAAM,CAAC;AAAA,YAC3C;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,WAAW,IAAI,OAAO;AAC/B;AAEA,IAAM,aAAa,WAAW;AAAA,EAC5B,MAAM;AAAA,IACJ;AAAA,IAEA,YAAY,MAAkB;AAC5B,WAAK,cAAc,SAAS,IAAI;AAAA,IAClC;AAAA,IAEA,OAAO,QAAoB;AAEzB,UACE,OAAO,cACP,OAAO,gBACP,OAAO,mBACP,WAAW,OAAO,UAAU,MAAM,WAAW,OAAO,KAAK,GACzD;AACA,aAAK,cAAc,SAAS,OAAO,IAAI;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,aAAa,CAAC,MAAM,EAAE;AAAA,EACxB;AACF;AAUO,SAAS,OACd,OACA,KACkD;AAClD,QAAM,QAAQ,CAAC,MAAkB,MAAM,IAAI,YAAY,EAAE,MAAM,EAAE,EAAE;AACnE,aAAW,QAAQ,CAAC,GAAG,EAAE,GAAY;AACnC,aACM,OAA0B,WAAW,KAAK,EAAE,aAAa,KAAK,IAAI,GACtE,MACA,OAAO,KAAK,QACZ;AACA,YAAM,SAAS,KAAK,QAAQ,SAAS,UAAU,KAAK,QAAQ,SAAS;AACrE,UAAI,KAAK,SAAS,SAAS,CAAC;AAC1B,eAAO,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,MAAM,IAAI,EAAE;AAC1D,UAAI,KAAK,SAAS,UAAU,KAAK,SAAS,YAAY;AACpD,cAAM,MAAM,KAAK,SAAS,KAAK;AAC/B,eAAO,MAAM,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,QAAQ,CAAC,OAAoB,QACxC,OAAO,OAAO,GAAG,GAAG,OAAO;AAO7B,SAAS,iBAAiB,MAAkB,GAAW,GAAW;AAChE,QAAM,MAAM,KAAK,mBAAmB,EAAE,GAAG,EAAE,CAAC;AAC5C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,KAAK,KAAK,YAAY,IAAI,KAAK,IAAI,KAAK;AAC9C,MACE,CAAC,MACD,IAAI,GAAG,OACP,IAAI,GAAG,UACP,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,wBAAwB;AAErD,WAAO;AACT,SAAO,OAAO,KAAK,OAAO,IAAI,GAAG;AACnC;AAEA,IAAM,QACJ,OAAO,cAAc,eACrB,uBAAuB,KAAK,UAAU,YAAY,UAAU,SAAS;AACvE,IAAM,UAAU,CAAC,MACf,QAAQ,EAAE,UAAU,EAAE;AAGxB,IAAM,WAAW,YAAY,OAA4C;AACzE,IAAM,YAAY,WAAW,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAE/D,IAAM,aAAa,WAAW,OAAsB;AAAA,EAClD,QAAQ,MAAM,WAAW;AAAA,EACzB,OAAO,MAAM,IAAI;AACf,WAAO,KAAK,IAAI,GAAG,OAAO;AAC1B,eAAW,KAAK,GAAG,SAAS;AAC1B,UAAI,EAAE,GAAG,QAAQ,GAAG;AAClB,eAAO,EAAE,QACL,WAAW,IAAI,UAAU,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,EAAE,CAAC,IACxD,WAAW;AAAA,MACjB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,SAAS,CAAC,MAAMC,YAAW,YAAY,KAAK,CAAC;AAC/C,CAAC;AAOD,IAAM,WAAW,WAAW;AAAA,EAC1B,MAAM;AAAA,IAIJ,YAAqB,MAAkB;AAAlB;AAAA,IAAmB;AAAA,IAHxC,UAA2C;AAAA,IAC3C,MAAM;AAAA,IAIN,UAAU;AACR,YAAM,EAAE,KAAK,IAAI;AACjB,YAAM,OACJ,KAAK,OAAO,KAAK,UACb,iBAAiB,MAAM,KAAK,QAAQ,GAAG,KAAK,QAAQ,CAAC,IACrD;AACN,UAAI,UAA+C;AACnD,WAAK,MAAM,MAAM,UAAU,EAAE,QAAQ,GAAG,KAAK,MAAM,IAAI,QAAQ,CAAC,MAAM,OAAO;AAC3E,kBAAU,EAAE,MAAM,GAAG;AAAA,MACvB,CAAC;AACD,YAAM,OAAO,CAAC,GAAmB,MAC/B,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG;AACtC,YAAM,OAAO,QAAQ,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AACpD,UAAI,CAAC,KAAK,SAAS,IAAI,EAAG,MAAK,SAAS,EAAE,SAAS,SAAS,GAAG,IAAI,EAAE,CAAC;AAAA,IACxE;AAAA,EACF;AAAA,EACA;AAAA,IACE,eAAe;AAAA,MACb,UAAU,OAAO,MAAM;AACrB,YAAI,MAAM,WAAW,KAAK,CAAC,QAAQ,KAAK,EAAG,QAAO;AAClD,cAAM,OAAO,iBAAiB,MAAM,MAAM,SAAS,MAAM,OAAO;AAChE,YAAI,CAAC,KAAM,QAAO;AAClB,cAAM,eAAe;AACrB,gBAAQ,KAAK,KAAK,KAAK,GAAG;AAC1B,eAAO;AAAA,MACT;AAAA,MACA,UAAU,OAAO;AACf,aAAK,UAAU,EAAE,GAAG,MAAM,SAAS,GAAG,MAAM,QAAQ;AACpD,aAAK,MAAM,QAAQ,KAAK;AACxB,aAAK,QAAQ;AAAA,MACf;AAAA,MACA,aAAa;AACX,aAAK,UAAU;AACf,aAAK,QAAQ;AAAA,MACf;AAAA,MACA,QAAQ,OAAO;AACb,aAAK,MAAM,QAAQ,KAAK;AACxB,aAAK,QAAQ;AAAA,MACf;AAAA,MACA,MAAM,OAAO;AACX,aAAK,MAAM,QAAQ,KAAK;AACxB,aAAK,QAAQ;AAAA,MACf;AAAA,MACA,OAAO;AACL,aAAK,MAAM;AACX,aAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB;AAC9B,SAAO,CAAC,YAAY,YAAY,UAAU,SAAS;AACrD;",
6
6
  "names": ["EditorView", "EditorView"]
7
7
  }
@@ -1269,23 +1269,23 @@ var FlattenedDoc = class _FlattenedDoc {
1269
1269
  get to() {
1270
1270
  return this.from + this.text.length;
1271
1271
  }
1272
- static get(doc, from, to) {
1273
- let cached = flattened.get(doc);
1272
+ static get(doc2, from, to) {
1273
+ let cached = flattened.get(doc2);
1274
1274
  if (!cached || cached.from >= to || cached.to <= from) {
1275
- let flat = new _FlattenedDoc(from, doc.sliceString(from, to));
1276
- flattened.set(doc, flat);
1275
+ let flat = new _FlattenedDoc(from, doc2.sliceString(from, to));
1276
+ flattened.set(doc2, flat);
1277
1277
  return flat;
1278
1278
  }
1279
1279
  if (cached.from == from && cached.to == to)
1280
1280
  return cached;
1281
1281
  let { text, from: cachedFrom } = cached;
1282
1282
  if (cachedFrom > from) {
1283
- text = doc.sliceString(from, cachedFrom) + text;
1283
+ text = doc2.sliceString(from, cachedFrom) + text;
1284
1284
  cachedFrom = from;
1285
1285
  }
1286
1286
  if (cached.to < to)
1287
- text += doc.sliceString(cached.to, to);
1288
- flattened.set(doc, new _FlattenedDoc(cachedFrom, text));
1287
+ text += doc2.sliceString(cached.to, to);
1288
+ flattened.set(doc2, new _FlattenedDoc(cachedFrom, text));
1289
1289
  return new _FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
1290
1290
  }
1291
1291
  };
@@ -1511,11 +1511,11 @@ function stringCursor(spec, state, from, to) {
1511
1511
  test = wrapStringTest(spec.test, state, test);
1512
1512
  return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? void 0 : (x) => x.toLowerCase(), test);
1513
1513
  }
1514
- function stringWordTest(doc, categorizer) {
1514
+ function stringWordTest(doc2, categorizer) {
1515
1515
  return (from, to, buf, bufPos) => {
1516
1516
  if (bufPos > from || bufPos + buf.length < to) {
1517
1517
  bufPos = Math.max(0, from - 2);
1518
- buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
1518
+ buf = doc2.sliceString(bufPos, Math.min(doc2.length, to + 2));
1519
1519
  }
1520
1520
  return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word || categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) && (categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word || categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);
1521
1521
  };
@@ -2066,7 +2066,7 @@ var searchExtensions = [
2066
2066
  ];
2067
2067
 
2068
2068
  // src/extensions/markdown.ts
2069
- import { EditorView as EditorView7, keymap as keymap3 } from "@codemirror/view";
2069
+ import { EditorView as EditorView9, keymap as keymap5 } from "@codemirror/view";
2070
2070
 
2071
2071
  // src/extensions/formatting.ts
2072
2072
  import { EditorSelection as EditorSelection2 } from "@codemirror/state";
@@ -2189,14 +2189,14 @@ function toggle(view, from) {
2189
2189
  var doneMark = Decoration2.mark({ class: "cm-task-done" });
2190
2190
  function build(view) {
2191
2191
  const builder = new RangeSetBuilder2();
2192
- const { doc } = view.state;
2192
+ const { doc: doc2 } = view.state;
2193
2193
  for (const { from, to } of view.visibleRanges) {
2194
2194
  syntaxTree(view.state).iterate({
2195
2195
  from,
2196
2196
  to,
2197
2197
  enter: (node) => {
2198
2198
  if (node.name !== "TaskMarker") return;
2199
- const checked = doc.sliceString(node.from + 1, node.to - 1) !== " ";
2199
+ const checked = doc2.sliceString(node.from + 1, node.to - 1) !== " ";
2200
2200
  builder.add(
2201
2201
  node.from,
2202
2202
  node.to,
@@ -2234,8 +2234,6 @@ import {
2234
2234
  ViewPlugin as ViewPlugin3,
2235
2235
  WidgetType as WidgetType2
2236
2236
  } from "@codemirror/view";
2237
- var numberMark = Decoration3.mark({ class: "cm-md-hang cm-md-hang-number" });
2238
- var quoteMark = Decoration3.mark({ class: "cm-md-hang cm-md-hang-quote" });
2239
2237
  var hidden = Decoration3.replace({});
2240
2238
  var taskMark = Decoration3.mark({ class: "cm-md-hang", inclusive: true });
2241
2239
  var HeadingLabel = class extends WidgetType2 {
@@ -2301,15 +2299,6 @@ function renderedNumber(state, mark) {
2301
2299
  const start = firstMark ? parseInt(state.doc.sliceString(firstMark.from, firstMark.to), 10) : 1;
2302
2300
  return `${(Number.isNaN(start) ? 1 : start) + index}${delimiter}`;
2303
2301
  }
2304
- function caretLines(state) {
2305
- const lines = /* @__PURE__ */ new Set();
2306
- for (const r of state.selection.ranges) {
2307
- const first = state.doc.lineAt(r.from).number;
2308
- const last = state.doc.lineAt(r.to).number;
2309
- for (let n = first; n <= last; n++) lines.add(n);
2310
- }
2311
- return lines;
2312
- }
2313
2302
  function skipSpaces(state, pos) {
2314
2303
  const line = state.doc.lineAt(pos);
2315
2304
  while (pos < line.to && state.doc.sliceString(pos, pos + 1) === " ") pos++;
@@ -2317,8 +2306,7 @@ function skipSpaces(state, pos) {
2317
2306
  }
2318
2307
  function build2(view) {
2319
2308
  const { state } = view;
2320
- const { doc } = state;
2321
- const editing = caretLines(state);
2309
+ const { doc: doc2 } = state;
2322
2310
  const decos = [];
2323
2311
  for (const { from, to } of view.visibleRanges) {
2324
2312
  syntaxTree2(state).iterate({
@@ -2329,21 +2317,20 @@ function build2(view) {
2329
2317
  const heading = /^ATXHeading(\d)$/.exec(name);
2330
2318
  if (heading) {
2331
2319
  const mark = node.node.firstChild;
2332
- if (!mark || mark.name !== "HeaderMark" || mark.from !== node.from)
2333
- return;
2320
+ if (!mark || mark.name !== "HeaderMark") return;
2334
2321
  decos.push(
2335
2322
  Decoration3.replace({
2336
2323
  widget: new HeadingLabel(Number(heading[1]))
2337
- }).range(mark.from, skipSpaces(state, mark.to))
2324
+ }).range(doc2.lineAt(node.from).from, skipSpaces(state, mark.to))
2338
2325
  );
2339
2326
  return;
2340
2327
  }
2341
2328
  if (name === "ListMark") {
2342
2329
  const end = skipSpaces(state, node.to);
2343
- const char = doc.sliceString(node.from, node.to);
2330
+ const char = doc2.sliceString(node.from, node.to);
2344
2331
  const bullet = ["-", "+", "*"].includes(char);
2345
2332
  const task = node.node.nextSibling?.firstChild;
2346
- const isTask = bullet && task?.name === "TaskMarker" && doc.sliceString(end, task.from).trim() === "";
2333
+ const isTask = bullet && task?.name === "TaskMarker" && doc2.sliceString(end, task.from).trim() === "";
2347
2334
  if (isTask && task) {
2348
2335
  if (end > node.from) decos.push(hidden.range(node.from, end));
2349
2336
  decos.push(taskMark.range(task.from, skipSpaces(state, task.to)));
@@ -2353,8 +2340,6 @@ function build2(view) {
2353
2340
  decos.push(
2354
2341
  Decoration3.replace({ widget: new Bullet() }).range(node.from, end)
2355
2342
  );
2356
- } else if (editing.has(doc.lineAt(node.from).number)) {
2357
- decos.push(numberMark.range(node.from, end));
2358
2343
  } else {
2359
2344
  decos.push(
2360
2345
  Decoration3.replace({
@@ -2367,10 +2352,10 @@ function build2(view) {
2367
2352
  if (name === "Blockquote") {
2368
2353
  for (let p = node.node.parent; p; p = p.parent)
2369
2354
  if (p.name === "Blockquote") return;
2370
- const first = doc.lineAt(node.from).number;
2371
- const last = doc.lineAt(node.to).number;
2355
+ const first = doc2.lineAt(node.from).number;
2356
+ const last = doc2.lineAt(node.to).number;
2372
2357
  for (let n = first; n <= last; n++) {
2373
- const line = doc.line(n);
2358
+ const line = doc2.line(n);
2374
2359
  const classes = ["cm-md-quote"];
2375
2360
  if (n === first) classes.push("cm-md-quote-first");
2376
2361
  if (n === last) classes.push("cm-md-quote-last");
@@ -2381,22 +2366,17 @@ function build2(view) {
2381
2366
  return;
2382
2367
  }
2383
2368
  if (name === "QuoteMark") {
2384
- const line = doc.lineAt(node.from);
2385
- const before = doc.sliceString(line.from, node.from);
2369
+ const line = doc2.lineAt(node.from);
2370
+ const before = doc2.sliceString(line.from, node.from);
2386
2371
  if (before.trim() !== "") return;
2387
2372
  let end = node.to;
2388
2373
  for (; ; ) {
2389
2374
  const next = skipSpaces(state, end);
2390
- if (doc.sliceString(next, next + 1) !== ">") break;
2375
+ if (doc2.sliceString(next, next + 1) !== ">") break;
2391
2376
  end = next + 1;
2392
2377
  }
2393
2378
  end = skipSpaces(state, end);
2394
- decos.push(
2395
- (editing.has(line.number) ? quoteMark : hidden).range(
2396
- line.from,
2397
- end
2398
- )
2399
- );
2379
+ decos.push(hidden.range(line.from, end));
2400
2380
  }
2401
2381
  }
2402
2382
  });
@@ -2410,7 +2390,7 @@ var marginPlugin = ViewPlugin3.fromClass(
2410
2390
  this.decorations = build2(view);
2411
2391
  }
2412
2392
  update(update) {
2413
- if (update.docChanged || update.selectionSet || update.viewportChanged || syntaxTree2(update.startState) !== syntaxTree2(update.state)) {
2393
+ if (update.docChanged || update.viewportChanged || syntaxTree2(update.startState) !== syntaxTree2(update.state)) {
2414
2394
  this.decorations = build2(update.view);
2415
2395
  }
2416
2396
  }
@@ -2500,7 +2480,6 @@ var margin = [marginPlugin, marginTheme];
2500
2480
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
2501
2481
  import {
2502
2482
  EditorSelection as EditorSelection3,
2503
- EditorState as EditorState2,
2504
2483
  Prec as Prec2
2505
2484
  } from "@codemirror/state";
2506
2485
  import { keymap } from "@codemirror/view";
@@ -2517,31 +2496,6 @@ function headingPrefix(state, line) {
2517
2496
  }
2518
2497
  return null;
2519
2498
  }
2520
- var keepOutOfPrefix = EditorState2.transactionFilter.of((tr) => {
2521
- if (!tr.selection) return tr;
2522
- const { state } = tr;
2523
- const before = tr.startState.selection;
2524
- let moved = false;
2525
- const ranges = tr.selection.ranges.map((r, i) => {
2526
- if (!r.empty) return r;
2527
- const line = state.doc.lineAt(r.head);
2528
- if (r.head >= line.to && line.length > 0) return r;
2529
- const prefix = headingPrefix(state, line);
2530
- if (!prefix || r.head >= prefix.to) return r;
2531
- const old = before.ranges[i] ?? before.main;
2532
- const leaving = !tr.docChanged && old.empty && old.head === prefix.to && line.from > 0;
2533
- moved = true;
2534
- return EditorSelection3.cursor(leaving ? line.from - 1 : prefix.to);
2535
- });
2536
- if (!moved) return tr;
2537
- return [
2538
- tr,
2539
- {
2540
- selection: EditorSelection3.create(ranges, tr.selection.mainIndex),
2541
- sequential: true
2542
- }
2543
- ];
2544
- });
2545
2499
  function selectedLines(state) {
2546
2500
  const seen = /* @__PURE__ */ new Set();
2547
2501
  const lines = [];
@@ -2556,12 +2510,12 @@ function selectedLines(state) {
2556
2510
  }
2557
2511
  var setHeading = (level) => ({ state, dispatch }) => {
2558
2512
  const lines = selectedLines(state);
2559
- const prefixes = lines.map((line) => headingPrefix(state, line));
2560
- const already = level > 0 && prefixes.every((p) => p && p.level === level);
2513
+ const prefixes2 = lines.map((line) => headingPrefix(state, line));
2514
+ const already = level > 0 && prefixes2.every((p) => p && p.level === level);
2561
2515
  const target = already ? 0 : level;
2562
2516
  const insert = target ? "#".repeat(target) + " " : "";
2563
2517
  const changes = lines.map((line, i) => {
2564
- const p = prefixes[i];
2518
+ const p = prefixes2[i];
2565
2519
  return { from: line.from, to: p ? p.to : line.from, insert };
2566
2520
  });
2567
2521
  dispatch(
@@ -2580,18 +2534,6 @@ function atHeadingStart(state) {
2580
2534
  const prefix = headingPrefix(state, line);
2581
2535
  return prefix && sel.main.head === prefix.to ? prefix : null;
2582
2536
  }
2583
- var backspace = ({ state, dispatch }) => {
2584
- const prefix = atHeadingStart(state);
2585
- if (!prefix) return false;
2586
- dispatch(
2587
- state.update({
2588
- changes: { from: prefix.from, to: prefix.to },
2589
- selection: EditorSelection3.cursor(prefix.from),
2590
- userEvent: "delete.backward"
2591
- })
2592
- );
2593
- return true;
2594
- };
2595
2537
  var enter = ({ state, dispatch }) => {
2596
2538
  const prefix = atHeadingStart(state);
2597
2539
  if (!prefix) return false;
@@ -2607,29 +2549,11 @@ var enter = ({ state, dispatch }) => {
2607
2549
  );
2608
2550
  return true;
2609
2551
  };
2610
- var deleteForward = ({ state, dispatch }) => {
2611
- const sel = state.selection;
2612
- if (sel.ranges.length !== 1 || !sel.main.empty) return false;
2613
- const line = state.doc.lineAt(sel.main.head);
2614
- if (sel.main.head !== line.to || line.number >= state.doc.lines) return false;
2615
- const prefix = headingPrefix(state, state.doc.line(line.number + 1));
2616
- if (!prefix) return false;
2617
- dispatch(
2618
- state.update({
2619
- changes: { from: line.to, to: prefix.to },
2620
- userEvent: "delete.forward"
2621
- })
2622
- );
2623
- return true;
2624
- };
2625
2552
  var headings = [
2626
- keepOutOfPrefix,
2627
- // above lang-markdown's own (Prec.high) Enter and Backspace.
2553
+ // above lang-markdown's own (Prec.high) Enter.
2628
2554
  Prec2.highest(
2629
2555
  keymap.of([
2630
- { key: "Backspace", run: backspace },
2631
2556
  { key: "Enter", run: enter },
2632
- { key: "Delete", run: deleteForward },
2633
2557
  { key: "Mod-Shift-h", run: setHeading(2), preventDefault: true },
2634
2558
  { key: "Mod-Shift-j", run: setHeading(3), preventDefault: true },
2635
2559
  { key: "Mod-Shift-b", run: setHeading(0), preventDefault: true },
@@ -2960,9 +2884,181 @@ var listsTheme = EditorView4.baseTheme({
2960
2884
  });
2961
2885
  var lists = [hideDoneField, menuPlugin, listsTheme];
2962
2886
 
2887
+ // src/extensions/prefix.ts
2888
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2889
+ import {
2890
+ EditorSelection as EditorSelection4,
2891
+ EditorState as EditorState2,
2892
+ Prec as Prec3
2893
+ } from "@codemirror/state";
2894
+ import { EditorView as EditorView5, keymap as keymap2 } from "@codemirror/view";
2895
+ var MARKS2 = /* @__PURE__ */ new Set(["QuoteMark", "ListMark", "HeaderMark", "TaskMarker"]);
2896
+ function skipSpaces2(state, pos, end) {
2897
+ while (pos < end && /[ \t]/.test(state.doc.sliceString(pos, pos + 1))) pos++;
2898
+ return pos;
2899
+ }
2900
+ function blockPrefix(state, line) {
2901
+ const tree = syntaxTree5(state);
2902
+ const marks = [];
2903
+ const starts = [];
2904
+ let pos = line.from;
2905
+ for (; ; ) {
2906
+ const at = skipSpaces2(state, pos, line.to);
2907
+ const node = tree.resolveInner(at, 1);
2908
+ if (!MARKS2.has(node.name) || node.from !== at || node.to <= at) break;
2909
+ if (doc(state, pos, at).trim() !== "") break;
2910
+ marks.push(node.name);
2911
+ starts.push(at);
2912
+ pos = node.to;
2913
+ }
2914
+ if (!marks.length) return null;
2915
+ return { from: line.from, to: skipSpaces2(state, pos, line.to), marks, starts };
2916
+ }
2917
+ var doc = (state, from, to) => state.doc.sliceString(from, to);
2918
+ var keepOut = EditorState2.transactionFilter.of((tr) => {
2919
+ if (!tr.selection) return tr;
2920
+ const { state } = tr;
2921
+ const before = tr.startState.selection;
2922
+ let moved = false;
2923
+ const ranges = tr.selection.ranges.map((r, i) => {
2924
+ if (!r.empty) return r;
2925
+ const line = state.doc.lineAt(r.head);
2926
+ const prefix = blockPrefix(state, line);
2927
+ if (!prefix || r.head >= prefix.to) return r;
2928
+ const old = before.ranges[i] ?? before.main;
2929
+ const leaving = !tr.docChanged && old.empty && old.head === prefix.to && line.from > 0;
2930
+ moved = true;
2931
+ return EditorSelection4.cursor(leaving ? line.from - 1 : prefix.to);
2932
+ });
2933
+ if (!moved) return tr;
2934
+ return [
2935
+ tr,
2936
+ {
2937
+ selection: EditorSelection4.create(ranges, tr.selection.mainIndex),
2938
+ sequential: true
2939
+ }
2940
+ ];
2941
+ });
2942
+ function atTextStart(state) {
2943
+ const sel = state.selection;
2944
+ if (sel.ranges.length !== 1 || !sel.main.empty) return null;
2945
+ const prefix = blockPrefix(state, state.doc.lineAt(sel.main.head));
2946
+ return prefix && sel.main.head === prefix.to ? prefix : null;
2947
+ }
2948
+ var backspace = ({ state, dispatch }) => {
2949
+ const prefix = atTextStart(state);
2950
+ if (!prefix) return false;
2951
+ dispatch(
2952
+ state.update({
2953
+ changes: { from: prefix.starts.at(-1), to: prefix.to },
2954
+ selection: EditorSelection4.cursor(prefix.starts.at(-1)),
2955
+ userEvent: "delete.backward"
2956
+ })
2957
+ );
2958
+ return true;
2959
+ };
2960
+ var deleteForward = ({ state, dispatch }) => {
2961
+ const sel = state.selection;
2962
+ if (sel.ranges.length !== 1 || !sel.main.empty) return false;
2963
+ const line = state.doc.lineAt(sel.main.head);
2964
+ if (sel.main.head !== line.to || line.number >= state.doc.lines) return false;
2965
+ const prefix = blockPrefix(state, state.doc.line(line.number + 1));
2966
+ if (!prefix) return false;
2967
+ dispatch(
2968
+ state.update({
2969
+ changes: { from: line.to, to: prefix.to },
2970
+ userEvent: "delete.forward"
2971
+ })
2972
+ );
2973
+ return true;
2974
+ };
2975
+ var inCode = (state, pos) => {
2976
+ for (let n = syntaxTree5(state).resolveInner(pos, -1); n; n = n.parent) {
2977
+ if (/^(FencedCode|CodeBlock|CodeText|InlineCode)$/.test(n.name)) return true;
2978
+ }
2979
+ return false;
2980
+ };
2981
+ var LIST_OR_HEADING = /^(\d{1,9}[.)]|[-*+]|#{1,6})$/;
2982
+ var HEADING = /^#{1,6}$/;
2983
+ var replaceMarker = EditorView5.inputHandler.of((view, from, to, text) => {
2984
+ if (text !== " " || from !== to) return false;
2985
+ const { state } = view;
2986
+ const line = state.doc.lineAt(from);
2987
+ const prefix = blockPrefix(state, line);
2988
+ if (!prefix || inCode(state, from)) return false;
2989
+ const { marks, starts } = prefix;
2990
+ let typed = doc(state, prefix.to, from);
2991
+ let mine = marks.length - 1;
2992
+ if (!typed && prefix.to === from && marks.length > 1) {
2993
+ typed = doc(state, starts[mine], from);
2994
+ mine--;
2995
+ }
2996
+ if (!typed || marks[mine] === "QuoteMark") return false;
2997
+ if (marks[mine] === "TaskMarker" && marks[mine - 1] === "ListMark") mine--;
2998
+ const ok = marks[mine] === "HeaderMark" ? HEADING : LIST_OR_HEADING;
2999
+ if (!ok.test(typed)) return false;
3000
+ view.dispatch({
3001
+ changes: { from: starts[mine], to: from, insert: typed + " " },
3002
+ selection: EditorSelection4.cursor(starts[mine] + typed.length + 1),
3003
+ userEvent: "input.type"
3004
+ });
3005
+ return true;
3006
+ });
3007
+ var prefixes = [
3008
+ keepOut,
3009
+ replaceMarker,
3010
+ // above lang-markdown's own (Prec.high) Backspace.
3011
+ Prec3.highest(
3012
+ keymap2.of([
3013
+ { key: "Backspace", run: backspace },
3014
+ { key: "Delete", run: deleteForward }
3015
+ ])
3016
+ )
3017
+ ];
3018
+
3019
+ // src/extensions/vertical.ts
3020
+ import { EditorSelection as EditorSelection5, Prec as Prec4 } from "@codemirror/state";
3021
+ import { cursorLineDown, cursorLineUp } from "@codemirror/commands";
3022
+ import { keymap as keymap3 } from "@codemirror/view";
3023
+ var streaks = /* @__PURE__ */ new WeakMap();
3024
+ var vertical = (move) => (view) => {
3025
+ const { state } = view;
3026
+ const sel = state.selection;
3027
+ if (sel.ranges.length !== 1 || !sel.main.empty) return move(view);
3028
+ let streak = streaks.get(view);
3029
+ if (!streak || streak.head !== sel.main.head || streak.doc !== state.doc) {
3030
+ streak = { visited: /* @__PURE__ */ new Map(), head: sel.main.head, doc: state.doc };
3031
+ }
3032
+ const from = state.doc.lineAt(sel.main.head).number;
3033
+ if (!streak.visited.has(from)) streak.visited.set(from, sel.main.head);
3034
+ if (!move(view)) return false;
3035
+ const now = view.state.selection.main;
3036
+ const to = view.state.doc.lineAt(now.head).number;
3037
+ const back = streak.visited.get(to);
3038
+ if (back !== void 0 && back !== now.head && view.state.doc === streak.doc) {
3039
+ view.dispatch({
3040
+ selection: EditorSelection5.cursor(back),
3041
+ scrollIntoView: true,
3042
+ userEvent: "select"
3043
+ });
3044
+ }
3045
+ streak.head = view.state.selection.main.head;
3046
+ streak.doc = view.state.doc;
3047
+ streaks.set(view, streak);
3048
+ return true;
3049
+ };
3050
+ var verticalMemory = Prec4.high(
3051
+ keymap3.of([
3052
+ { key: "ArrowUp", run: vertical(cursorLineUp) },
3053
+ { key: "ArrowDown", run: vertical(cursorLineDown) },
3054
+ { mac: "Ctrl-p", run: vertical(cursorLineUp) },
3055
+ { mac: "Ctrl-n", run: vertical(cursorLineDown) }
3056
+ ])
3057
+ );
3058
+
2963
3059
  // src/extensions/zoom.ts
2964
- import { Prec as Prec3 } from "@codemirror/state";
2965
- import { keymap as keymap2 } from "@codemirror/view";
3060
+ import { Prec as Prec5 } from "@codemirror/state";
3061
+ import { keymap as keymap4 } from "@codemirror/view";
2966
3062
  var MIN_FONT_SIZE = 6;
2967
3063
  var FALLBACK_FONT_SIZE = 16;
2968
3064
  var px = (value) => parseFloat(value) || FALLBACK_FONT_SIZE;
@@ -2991,8 +3087,8 @@ function step(view, by) {
2991
3087
  var zoomIn = (view) => step(view, 1);
2992
3088
  var zoomOut = (view) => step(view, -1);
2993
3089
  var zoomReset = (view) => step(view, 0);
2994
- var markdownZoom = Prec3.high(
2995
- keymap2.of([
3090
+ var markdownZoom = Prec5.high(
3091
+ keymap4.of([
2996
3092
  // `+` and `_` are the shifted keys, for keyboards that need Shift.
2997
3093
  { key: "Mod-=", run: zoomIn, preventDefault: true },
2998
3094
  { key: "Mod-+", run: zoomIn, preventDefault: true },
@@ -3004,7 +3100,7 @@ var markdownZoom = Prec3.high(
3004
3100
 
3005
3101
  // src/themes/markdown.ts
3006
3102
  import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
3007
- import { EditorView as EditorView6 } from "@codemirror/view";
3103
+ import { EditorView as EditorView8 } from "@codemirror/view";
3008
3104
  var bodyFont = (style) => style == "serif" ? 'var(--text-editor-family-serif, "Merriweather", serif)' : "var(--text-editor-family-sans)";
3009
3105
  var zoomed2 = (length) => `calc(${length} * var(--markdown-zoom, 1))`;
3010
3106
  var markdownStyles = (style) => ({
@@ -3219,7 +3315,7 @@ var markdownSyntaxHighlighting = (style) => HighlightStyle.define([
3219
3315
  { tag: tags.monospace, ...baseCodeStyles }
3220
3316
  ]);
3221
3317
  var theme = (style) => [
3222
- EditorView6.theme(markdownStyles(style)),
3318
+ EditorView8.theme(markdownStyles(style)),
3223
3319
  syntaxHighlighting(markdownSyntaxHighlighting(style))
3224
3320
  ];
3225
3321
 
@@ -3229,7 +3325,7 @@ function markdownExtensions() {
3229
3325
  ...theme("sans"),
3230
3326
  indentOnInput(),
3231
3327
  markdownZoom,
3232
- keymap3.of([
3328
+ keymap5.of([
3233
3329
  ...markdownFormattingKeymap,
3234
3330
  ...defaultKeymap,
3235
3331
  ...searchKeymap,
@@ -3237,7 +3333,7 @@ function markdownExtensions() {
3237
3333
  ...completionKeymap,
3238
3334
  indentWithTab
3239
3335
  ]),
3240
- EditorView7.lineWrapping,
3336
+ EditorView9.lineWrapping,
3241
3337
  // GFM base: task lists, tables, strikethrough, autolinks.
3242
3338
  markdown({
3243
3339
  base: markdownLanguage,
@@ -3246,6 +3342,8 @@ function markdownExtensions() {
3246
3342
  }),
3247
3343
  taskCheckboxes,
3248
3344
  margin,
3345
+ prefixes,
3346
+ verticalMemory,
3249
3347
  headings,
3250
3348
  lists,
3251
3349
  indentUnit.of(" ")