@latentic/live-markdown 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/index.d.ts +805 -0
- package/dist/index.js +5804 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +36 -0
- package/package.json +90 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import * as _codemirror_view from '@codemirror/view';
|
|
5
|
+
import { Decoration, ViewPlugin, DecorationSet, ViewUpdate, EditorView, Command, KeyBinding } from '@codemirror/view';
|
|
6
|
+
import * as _codemirror_state from '@codemirror/state';
|
|
7
|
+
import { EditorState, Facet, Extension } from '@codemirror/state';
|
|
8
|
+
import { syntaxTree } from '@codemirror/language';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Core value types for the editor's public surface.
|
|
12
|
+
*
|
|
13
|
+
* `SourceRange` is a half-open byte range into the document; `DocumentTextChange`
|
|
14
|
+
* describes one edit. They are structurally identical to the host app's own
|
|
15
|
+
* range/change types, so a host can pass its `onChange` straight through without
|
|
16
|
+
* adapting — structural typing makes them interchangeable.
|
|
17
|
+
*/
|
|
18
|
+
/** A half-open range into the document, in UTF-8 byte offsets. */
|
|
19
|
+
interface SourceRange {
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
}
|
|
23
|
+
/** One text edit: the replaced byte range and the text inserted in its place. */
|
|
24
|
+
interface DocumentTextChange {
|
|
25
|
+
range: SourceRange;
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rich copy/cut (#135): every copy writes BOTH clipboard flavors —
|
|
31
|
+
*
|
|
32
|
+
* text/plain — the markdown SOURCE of the selection (lossless: editors,
|
|
33
|
+
* terminals, and Compose→Compose round trips);
|
|
34
|
+
* text/html — the selection rendered by the host (Google Docs, Slack,
|
|
35
|
+
* Word, and Gmail paste it formatted).
|
|
36
|
+
*
|
|
37
|
+
* The HTML carries {@link COMPOSE_CLIPBOARD_ATTR} so our own paste handler
|
|
38
|
+
* prefers the markdown flavor instead of re-converting our rendering.
|
|
39
|
+
*
|
|
40
|
+
* Stays out of the way of: the table surface's TSV cell-selection copy (a
|
|
41
|
+
* capture-phase document listener that preventDefaults first), and CM's
|
|
42
|
+
* native empty-selection behavior. Without a host renderer the handler still
|
|
43
|
+
* runs — plain-only, but through one code path.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/** Host-supplied markdown → HTML renderer for clipboard writes. Null (the
|
|
47
|
+
* default) copies plain markdown only. Wired like the other host seams —
|
|
48
|
+
* see hostFacets.ts. */
|
|
49
|
+
type RenderClipboardHtml = (markdown: string) => string | null;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The rendering contract: one polymorphic shape for "how does a Lezer node
|
|
53
|
+
* render", replacing three parallel dispatch surfaces (a data-entry union, a
|
|
54
|
+
* widget-name union + builders map, and a contextual-override switch).
|
|
55
|
+
*
|
|
56
|
+
* NodeRule = (ctx) => Paint — one function per node NAME
|
|
57
|
+
* Paint = which CM6 mechanism — a CLOSED union
|
|
58
|
+
*
|
|
59
|
+
* The openness is split on the right axis: CONSTRUCTS grow (every new node
|
|
60
|
+
* adds a rule — one entry, one place, usually one line via the combinators
|
|
61
|
+
* below), while the ways to PAINT don't (line class / span mark / hide /
|
|
62
|
+
* widget / nothing — CodeMirror's own vocabulary). The single switch over
|
|
63
|
+
* `Paint` lives in the painter (plugin.ts) and never changes when a
|
|
64
|
+
* construct is added.
|
|
65
|
+
*
|
|
66
|
+
* Context (the bare-URL lesson: a node name can mean different things in
|
|
67
|
+
* different parents) is not a bolt-on — every rule IS a function of context;
|
|
68
|
+
* simple rules just ignore it.
|
|
69
|
+
*
|
|
70
|
+
* Extensions contribute rules through {@link nodeRulesFacet}: a plugin that
|
|
71
|
+
* introduces node names ships its rules alongside its grammar, touching no
|
|
72
|
+
* core file.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
interface NodeLike {
|
|
76
|
+
readonly name: string;
|
|
77
|
+
readonly from: number;
|
|
78
|
+
readonly to: number;
|
|
79
|
+
readonly parent: NodeLike | null;
|
|
80
|
+
readonly firstChild: NodeLike | null;
|
|
81
|
+
readonly nextSibling: NodeLike | null;
|
|
82
|
+
readonly prevSibling: NodeLike | null;
|
|
83
|
+
getChild(type: string): NodeLike | null;
|
|
84
|
+
}
|
|
85
|
+
/** Everything a rule may consult. */
|
|
86
|
+
interface NodeContext {
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly from: number;
|
|
89
|
+
readonly to: number;
|
|
90
|
+
/** The parent node's name — the common contextual discriminator. */
|
|
91
|
+
readonly parentName: string | undefined;
|
|
92
|
+
/** Full structural node, for rules that need siblings/children. */
|
|
93
|
+
readonly node: NodeLike;
|
|
94
|
+
/** Rules are pure over state — no view dependency, so non-editor
|
|
95
|
+
* renderers (table cells) can invoke the same rules. */
|
|
96
|
+
readonly state: EditorState;
|
|
97
|
+
}
|
|
98
|
+
/** One painting instruction, in CodeMirror's own vocabulary. */
|
|
99
|
+
type Paint =
|
|
100
|
+
/** Stamp a line class — on the node's first line, or every spanned line. */
|
|
101
|
+
{
|
|
102
|
+
readonly paint: "lineClass";
|
|
103
|
+
readonly className: string;
|
|
104
|
+
readonly span: "first" | "all";
|
|
105
|
+
}
|
|
106
|
+
/** Style the node's span. */
|
|
107
|
+
| {
|
|
108
|
+
readonly paint: "mark";
|
|
109
|
+
readonly className: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Hide a range (default: the node, plus its one separator space when
|
|
113
|
+
* line-leading) and make it atomic to caret motion. `atomicTo` widens the
|
|
114
|
+
* atom past the hidden range (marker + space move as one unit).
|
|
115
|
+
*/
|
|
116
|
+
| {
|
|
117
|
+
readonly paint: "hide";
|
|
118
|
+
readonly range?: {
|
|
119
|
+
readonly from: number;
|
|
120
|
+
readonly to: number;
|
|
121
|
+
};
|
|
122
|
+
readonly expandSpace?: boolean;
|
|
123
|
+
readonly atomicTo?: number;
|
|
124
|
+
}
|
|
125
|
+
/** Replace the (hidden) span with a widget decoration. */
|
|
126
|
+
| {
|
|
127
|
+
readonly paint: "widget";
|
|
128
|
+
readonly deco: Decoration;
|
|
129
|
+
readonly atomicTo?: number;
|
|
130
|
+
}
|
|
131
|
+
/** Leave the node alone — visible raw source. */
|
|
132
|
+
| {
|
|
133
|
+
readonly paint: "none";
|
|
134
|
+
};
|
|
135
|
+
interface RuleMeta {
|
|
136
|
+
readonly intent: "render-raw" | "structural";
|
|
137
|
+
readonly why: string;
|
|
138
|
+
}
|
|
139
|
+
/** How one node name renders. `meta` tags the deliberate do-nothing rules so
|
|
140
|
+
* the coverage test can insist their reason is documented. */
|
|
141
|
+
type NodeRule = ((ctx: NodeContext) => Paint) & {
|
|
142
|
+
readonly meta?: RuleMeta;
|
|
143
|
+
};
|
|
144
|
+
type NodeRules = Readonly<Record<string, NodeRule>>;
|
|
145
|
+
/** Style the node's span with a class. */
|
|
146
|
+
declare function mark(className: string): NodeRule;
|
|
147
|
+
/** Stamp a line class on every line the node spans. */
|
|
148
|
+
declare function line(className: string): NodeRule;
|
|
149
|
+
/** Stamp a line class on the node's first line only (headings). */
|
|
150
|
+
declare function headingLine(className: string): NodeRule;
|
|
151
|
+
/** Hide the node (marker chrome), atomically. */
|
|
152
|
+
declare function hideAlways(): NodeRule;
|
|
153
|
+
/** Deliberately unstyled-for-now, visible raw — `why` documents the intent. */
|
|
154
|
+
declare function raw(why: string): NodeRule;
|
|
155
|
+
/** A parser grouping construct, never directly visible — `why` says which. */
|
|
156
|
+
declare function structural(why: string): NodeRule;
|
|
157
|
+
/**
|
|
158
|
+
* Rules contributed by extensions, merged over the base table (an extension
|
|
159
|
+
* may also deliberately override a base rule — last provider wins). The
|
|
160
|
+
* painter reads THIS, never the base table directly.
|
|
161
|
+
*/
|
|
162
|
+
declare const nodeRulesFacet: Facet<Readonly<Record<string, NodeRule>>, Readonly<Record<string, NodeRule>>>;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A syntax tree that reaches `pos`.
|
|
166
|
+
*
|
|
167
|
+
* `syntaxTree` returns only what the **viewport** has driven the parser
|
|
168
|
+
* through, so `resolveInner` past that point reports the position as bare
|
|
169
|
+
* `Document`. Every command that asks "am I inside a list / a fence / a table?"
|
|
170
|
+
* then gets "no" and takes the plain-text path: a wrong answer, not a slow one.
|
|
171
|
+
*
|
|
172
|
+
* Measured in WebKit, the engine we ship on, with real layout — two regimes.
|
|
173
|
+
*
|
|
174
|
+
* On the frame a document opens the parser has covered a few thousand
|
|
175
|
+
* characters: 3,009 of 7,902, viewport 0–331, for an editor 11,404px tall. The
|
|
176
|
+
* idle parse then catches up within about half a second, so near the viewport
|
|
177
|
+
* this is a race — and it heals before a hand-test can see it.
|
|
178
|
+
*
|
|
179
|
+
* Further out it never heals. The idle parse stops 100,000 characters past the
|
|
180
|
+
* viewport: the tree plateaus at 100,739 for a 167k-character document and a
|
|
181
|
+
* 341k one alike, and stays there. `ensureSyntaxTree` resumes from that plateau
|
|
182
|
+
* and finishes the 341k document in 43ms — inside the budget above.
|
|
183
|
+
*
|
|
184
|
+
* Only for questions about a *position*. Decoration plugins iterate
|
|
185
|
+
* `view.visibleRanges` and should keep using `syntaxTree` directly: there,
|
|
186
|
+
* being limited to the viewport is the point.
|
|
187
|
+
*/
|
|
188
|
+
type Tree = ReturnType<typeof syntaxTree>;
|
|
189
|
+
declare function treeAt(state: EditorState, pos: number): Tree;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Markdown decoration plugin — the PAINTER. Walks the Lezer tree against the
|
|
193
|
+
* visible viewport, asks each node's {@link NodeRule} how to render, and
|
|
194
|
+
* applies the returned {@link Paint}.
|
|
195
|
+
*
|
|
196
|
+
* The painter contains *no* construct knowledge: rules live in the base
|
|
197
|
+
* table (`NODE_RULES`) merged with any extension-contributed rules
|
|
198
|
+
* (`nodeRulesFacet`). The one switch here is over `Paint` — CodeMirror's
|
|
199
|
+
* closed set of mechanisms — so adding a construct, widget, contextual
|
|
200
|
+
* override, or whole extension never edits this file.
|
|
201
|
+
*
|
|
202
|
+
* Perf shape (unchanged from the spike):
|
|
203
|
+
* * Walks `view.visibleRanges` against `syntaxTree(view.state)` —
|
|
204
|
+
* the work is proportional to the viewport, not the document.
|
|
205
|
+
* * Rebuilt on doc change or viewport change.
|
|
206
|
+
*
|
|
207
|
+
* Decoration ordering (CM6 requires `from`-then-`startSide` ascending):
|
|
208
|
+
* * Line decorations collected in one bucket, mark / replace in
|
|
209
|
+
* another. The final `Decoration.set` lets CM6 sort defensively,
|
|
210
|
+
* costing ~µs on the typical viewport — cheaper than risking the
|
|
211
|
+
* "decorations out of order" runtime error.
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
declare const markdownDecorationsPlugin: ViewPlugin<{
|
|
215
|
+
decorations: DecorationSet;
|
|
216
|
+
atomic: DecorationSet;
|
|
217
|
+
update(update: ViewUpdate): void;
|
|
218
|
+
}, undefined>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Editor theme.
|
|
222
|
+
*
|
|
223
|
+
* Lives in `EditorView.baseTheme` rather than in `global.scss` for
|
|
224
|
+
* three load-bearing reasons:
|
|
225
|
+
*
|
|
226
|
+
* 1. **Line-metric integrity.** CM6 measures line heights from the
|
|
227
|
+
* DOM through a `requestMeasure` cycle to compute click → byte
|
|
228
|
+
* and scroll → viewport mappings. Styles injected via
|
|
229
|
+
* `EditorView.theme` participate in that cycle at the right
|
|
230
|
+
* moment; styles from an external sheet can land *after* the
|
|
231
|
+
* first measurement, so clicks land on the wrong line until the
|
|
232
|
+
* next measure fires. (This was the click-drift the spike
|
|
233
|
+
* shipped — fixed by moving here.)
|
|
234
|
+
* 2. **Specificity that beats the base theme without `!important`.**
|
|
235
|
+
* CM6 ships its monospace-and-purple base theme via the same
|
|
236
|
+
* mechanism; layering `EditorView.theme` on top is the only
|
|
237
|
+
* collision-free pattern.
|
|
238
|
+
* 3. **No margin/padding on heading lines.** Margins between
|
|
239
|
+
* `.cm-line` siblings shift visual position without changing
|
|
240
|
+
* `offsetTop`, so the metric cache and the eye disagree —
|
|
241
|
+
* that's the canonical click-on-wrong-line bug. We change
|
|
242
|
+
* `font:` (which doesn't touch line-height), nothing else.
|
|
243
|
+
*
|
|
244
|
+
* Zettlr's `markdown-editor/theme/editor.ts` is the canonical
|
|
245
|
+
* reference for this pattern in the open-source ecosystem.
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* `EditorView.theme` (not `baseTheme`) — `baseTheme` is the
|
|
249
|
+
* low-priority slot meant for theme packages and gets out-specifity'd
|
|
250
|
+
* by any app-level CSS (Carbon's globals in our case). `theme` is the
|
|
251
|
+
* app-priority slot; our intent is to override CM6's defaults, so
|
|
252
|
+
* this is the right tier.
|
|
253
|
+
*/
|
|
254
|
+
declare const editorBaseTheme: _codemirror_state.Extension;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Display-time resolution of image `src` values for the editor — the
|
|
258
|
+
* environment-agnostic pieces.
|
|
259
|
+
*
|
|
260
|
+
* Markdown stores image references **workspace-relative and portable** (e.g.
|
|
261
|
+
* `images/pasted-….png`), which is what lands on disk and survives moving the
|
|
262
|
+
* folder between machines. A WebView, though, can't load a bare relative path
|
|
263
|
+
* against its app origin, so a host that streams local files needs to map the
|
|
264
|
+
* reference onto its own asset protocol.
|
|
265
|
+
*
|
|
266
|
+
* This module provides the host-independent parts: the POSIX path helpers
|
|
267
|
+
* (`computeFileDir` / `joinPath` / …), the `hasUriScheme` guard, and
|
|
268
|
+
* `defaultResolveImageSrc` (render as-is — the browser/SSR default). A desktop
|
|
269
|
+
* host composes these with its file API (e.g. Tauri `convertFileSrc`) to build
|
|
270
|
+
* its own resolver and injects it via the editor's `resolveImageSrc` prop. The
|
|
271
|
+
* stored markdown reference is never rewritten — only the rendered `<img src>`.
|
|
272
|
+
*
|
|
273
|
+
* Paths are treated as POSIX (`/`), matching the macOS/Linux workspace folders
|
|
274
|
+
* this targets. Windows-style drive paths are passed through unresolved.
|
|
275
|
+
*/
|
|
276
|
+
interface ImageResolveContext {
|
|
277
|
+
/** Absolute OS directory of the markdown file being edited, or null. */
|
|
278
|
+
fileDir: string | null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* True when `src` already carries a URI scheme, a protocol-relative `//`, or a
|
|
282
|
+
* bare fragment — i.e. it's directly loadable and needs no path resolution. A
|
|
283
|
+
* host's display-src resolver uses this to decide whether to map a relative
|
|
284
|
+
* workspace path onto its own asset protocol.
|
|
285
|
+
*/
|
|
286
|
+
declare function hasUriScheme(src: string): boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Environment-agnostic default for the editor's `resolveImageSrcFacet`: render
|
|
289
|
+
* the reference as-is. Data URLs and absolute/schemed URLs load directly; a
|
|
290
|
+
* relative ref resolves against the page origin (or shows broken if no backing
|
|
291
|
+
* file). A desktop host overrides the facet with its own resolver, which maps
|
|
292
|
+
* relative paths onto a local asset protocol.
|
|
293
|
+
*/
|
|
294
|
+
declare function defaultResolveImageSrc(rawSrc: string, _ctx: ImageResolveContext): string;
|
|
295
|
+
/**
|
|
296
|
+
* The directory a relative image reference resolves against: the folder
|
|
297
|
+
* containing the active markdown file. Falls back to the workspace root when
|
|
298
|
+
* the file path is unknown, and to null when there's no workspace.
|
|
299
|
+
*/
|
|
300
|
+
declare function computeFileDir(workspaceRoot: string | null | undefined, filePath: string | null | undefined): string | null;
|
|
301
|
+
declare function isAbsolutePath(p: string): boolean;
|
|
302
|
+
/** POSIX `dirname`: the parent of a path, with trailing slashes ignored. */
|
|
303
|
+
declare function dirnamePath(p: string): string;
|
|
304
|
+
/**
|
|
305
|
+
* Join `rel` onto `dir`, normalizing `.` and `..` segments. Absolute-ness is
|
|
306
|
+
* inherited from `dir`; `..` never escapes above an absolute root.
|
|
307
|
+
*/
|
|
308
|
+
declare function joinPath(dir: string, rel: string): string;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Host-environment injection seams.
|
|
312
|
+
*
|
|
313
|
+
* The editor surface is environment-agnostic: it knows how to render and edit
|
|
314
|
+
* markdown, but NOT how to read/write files, resolve asset URLs, or open links —
|
|
315
|
+
* those depend on where it's embedded (a Tauri desktop shell, a plain browser, a
|
|
316
|
+
* server-rendered preview). Each capability is a CM6 facet with a sensible
|
|
317
|
+
* browser default; the React host overrides it by setting the facet from a prop.
|
|
318
|
+
*
|
|
319
|
+
* Keeping these as facets (not React context) lets the non-React CM6 plugins and
|
|
320
|
+
* widgets — image paste handlers, the inline `<img>` widget, the click model —
|
|
321
|
+
* read them straight off `view.state`.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
type ResolveImageSrc = (rawSrc: string, ctx: ImageResolveContext) => string;
|
|
325
|
+
type SaveImageBytes = (relPath: string, bytes: Uint8Array) => Promise<void>;
|
|
326
|
+
type OpenExternalUrl = (url: string) => void;
|
|
327
|
+
/** Viewport point a comment composer should anchor to (the right-click point). */
|
|
328
|
+
type CommentAnchor = {
|
|
329
|
+
x: number;
|
|
330
|
+
y: number;
|
|
331
|
+
};
|
|
332
|
+
type CommentOnExcerpt = (excerpt: {
|
|
333
|
+
text: string;
|
|
334
|
+
range: SourceRange;
|
|
335
|
+
}, anchor: CommentAnchor) => void;
|
|
336
|
+
|
|
337
|
+
type CodeMirrorEditorMode = "wysiwyg" | "source";
|
|
338
|
+
/** A non-empty editor selection, in document byte offsets. */
|
|
339
|
+
interface EditorSelectionSnapshot {
|
|
340
|
+
range: SourceRange;
|
|
341
|
+
text: string;
|
|
342
|
+
}
|
|
343
|
+
interface CodeMirrorMarkdownEditorProps {
|
|
344
|
+
mode?: CodeMirrorEditorMode;
|
|
345
|
+
onChange: (value: string, changes: DocumentTextChange[]) => void;
|
|
346
|
+
value: string;
|
|
347
|
+
workspaceRoot?: string;
|
|
348
|
+
filePath?: string;
|
|
349
|
+
linkTargets?: ReadonlySet<string>;
|
|
350
|
+
onNavigateToLink?: (path: string) => void;
|
|
351
|
+
/**
|
|
352
|
+
* Host-rendered toolbar. The editor owns the live `EditorView` and hands it to
|
|
353
|
+
* the slot; the host builds whatever toolbar UI it wants (formatting buttons,
|
|
354
|
+
* file actions, …) around it. Omit for a chromeless editor. Return a STABLE
|
|
355
|
+
* element shape so the host's own memoisation can hold across keystrokes.
|
|
356
|
+
*/
|
|
357
|
+
toolbar?: (ctx: {
|
|
358
|
+
view: EditorView;
|
|
359
|
+
}) => ReactNode;
|
|
360
|
+
/**
|
|
361
|
+
* Host-rendered actions for the current text selection (e.g. a comment / ask
|
|
362
|
+
* bubble). Called with the live selection (or `null` when collapsed) and a
|
|
363
|
+
* `dismiss` that collapses the selection back to a caret. Omit for none.
|
|
364
|
+
*/
|
|
365
|
+
selectionActions?: (ctx: {
|
|
366
|
+
selection: EditorSelectionSnapshot | null;
|
|
367
|
+
dismiss: () => void;
|
|
368
|
+
}) => ReactNode;
|
|
369
|
+
/**
|
|
370
|
+
* Map a markdown image `src` to a loadable URL. Default: render the reference
|
|
371
|
+
* as-is. A desktop host maps workspace-relative paths onto its asset protocol.
|
|
372
|
+
*/
|
|
373
|
+
resolveImageSrc?: ResolveImageSrc;
|
|
374
|
+
/**
|
|
375
|
+
* Persist a pasted/dropped image's bytes at a workspace-relative path.
|
|
376
|
+
* Default: omitted ⇒ the image is inlined as a `data:` URL.
|
|
377
|
+
*/
|
|
378
|
+
saveImageBytes?: SaveImageBytes;
|
|
379
|
+
/**
|
|
380
|
+
* Open a clicked external link. Default: a new browser tab. A desktop host
|
|
381
|
+
* overrides this to leave the app's webview.
|
|
382
|
+
*/
|
|
383
|
+
onOpenExternalUrl?: OpenExternalUrl;
|
|
384
|
+
/**
|
|
385
|
+
* Comment on a selected table row/column — wired to the table context menu's
|
|
386
|
+
* "Comment on this row / column". The host opens its comment composer at the
|
|
387
|
+
* given anchor, seeded with the excerpt. Default: omitted ⇒ no such menu items.
|
|
388
|
+
*/
|
|
389
|
+
onCommentOnExcerpt?: CommentOnExcerpt;
|
|
390
|
+
/**
|
|
391
|
+
* Render a markdown selection to HTML for the clipboard, so a copy pastes
|
|
392
|
+
* formatted into Google Docs / Slack / Word (the markdown source always
|
|
393
|
+
* rides along as text/plain). Default: omitted ⇒ plain-only copies.
|
|
394
|
+
*/
|
|
395
|
+
renderClipboardHtml?: RenderClipboardHtml;
|
|
396
|
+
/**
|
|
397
|
+
* Called once, right after a tab-switch content swap commits and paints.
|
|
398
|
+
* Used by the host for latency instrumentation; no-op by default.
|
|
399
|
+
*/
|
|
400
|
+
onAfterContentSwap?: () => void;
|
|
401
|
+
/**
|
|
402
|
+
* Receives a synchronous `flush()` that pulls the editor's live (debounce-
|
|
403
|
+
* lagged) content into the last `onChange` immediately — the host calls it
|
|
404
|
+
* before persisting. `null` is passed on unmount. Lets a host avoid writing
|
|
405
|
+
* stale buffers on Cmd+S / tab close.
|
|
406
|
+
*/
|
|
407
|
+
onFlushReady?: (flush: (() => void) | null) => void;
|
|
408
|
+
}
|
|
409
|
+
declare function CodeMirrorMarkdownEditorInner({ mode, onChange, value, workspaceRoot, filePath, linkTargets, onNavigateToLink, toolbar, selectionActions, resolveImageSrc, saveImageBytes, onOpenExternalUrl, onCommentOnExcerpt, renderClipboardHtml, onAfterContentSwap, onFlushReady, }: CodeMirrorMarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
410
|
+
/**
|
|
411
|
+
* Memoised export — same reason as the Tiptap editor. AppShell
|
|
412
|
+
* re-renders on every chat-thread token; without memoisation each
|
|
413
|
+
* token would blow through the editor's render path.
|
|
414
|
+
*/
|
|
415
|
+
declare const CodeMirrorMarkdownEditor: react.MemoExoticComponent<typeof CodeMirrorMarkdownEditorInner>;
|
|
416
|
+
|
|
417
|
+
declare const formatCommands: {
|
|
418
|
+
toggleBold: Command;
|
|
419
|
+
toggleItalic: Command;
|
|
420
|
+
toggleInlineCode: Command;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
declare const blockCommands: {
|
|
424
|
+
toggleHeading1: Command;
|
|
425
|
+
toggleHeading2: Command;
|
|
426
|
+
toggleHeading3: Command;
|
|
427
|
+
toggleBulletList: Command;
|
|
428
|
+
toggleOrderedList: Command;
|
|
429
|
+
toggleTaskList: Command;
|
|
430
|
+
toggleBlockquote: Command;
|
|
431
|
+
toggleCodeBlock: Command;
|
|
432
|
+
insertTable: Command;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Editor-update fan-out that survives `view.setState`.
|
|
437
|
+
*
|
|
438
|
+
* Reactive chrome (toolbar pressed-states, the selection-actions bubble)
|
|
439
|
+
* needs a callback on every selection/doc change. Injecting an
|
|
440
|
+
* `EditorView.updateListener` with `StateEffect.appendConfig` only patches
|
|
441
|
+
* the CURRENT state's configuration — the next `view.setState` (tab switch,
|
|
442
|
+
* file reload) builds a fresh state from the base extensions and the
|
|
443
|
+
* listener silently dies, freezing the chrome at the previous document's
|
|
444
|
+
* context.
|
|
445
|
+
*
|
|
446
|
+
* The bus lives IN the base extensions, so every created state carries it,
|
|
447
|
+
* while subscriptions key on the `EditorView` — the object that is stable
|
|
448
|
+
* across state swaps.
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Subscribe to every update of `view`, across all its future states.
|
|
453
|
+
* Returns the unsubscribe function — call it in the effect cleanup.
|
|
454
|
+
*/
|
|
455
|
+
declare function onEditorUpdate(view: EditorView, fn: (update: ViewUpdate) => void): () => void;
|
|
456
|
+
|
|
457
|
+
interface ToolbarContribution {
|
|
458
|
+
readonly id: string;
|
|
459
|
+
readonly group: "heading" | "format" | "block" | "insert" | string;
|
|
460
|
+
readonly label: string;
|
|
461
|
+
readonly icon: ReactNode;
|
|
462
|
+
readonly shortcut?: string;
|
|
463
|
+
readonly isActive?: (caretContext: CaretContextSnapshot) => boolean;
|
|
464
|
+
readonly run: (view: _codemirror_view.EditorView) => void;
|
|
465
|
+
}
|
|
466
|
+
interface CaretContextSnapshot {
|
|
467
|
+
readonly bold: boolean;
|
|
468
|
+
readonly italic: boolean;
|
|
469
|
+
readonly code: boolean;
|
|
470
|
+
readonly link: boolean;
|
|
471
|
+
readonly heading: 1 | 2 | 3 | 4 | 5 | 6 | 0;
|
|
472
|
+
readonly bulletList: boolean;
|
|
473
|
+
readonly orderedList: boolean;
|
|
474
|
+
readonly blockquote: boolean;
|
|
475
|
+
}
|
|
476
|
+
interface MarkdownExtension {
|
|
477
|
+
readonly name: string;
|
|
478
|
+
readonly version: string;
|
|
479
|
+
readonly description?: string;
|
|
480
|
+
/** Node rules for constructs this extension's grammar introduces (or
|
|
481
|
+
* deliberately overrides) — merged into the decoration painter via
|
|
482
|
+
* `nodeRulesFacet`, so an extension never edits the base table. */
|
|
483
|
+
readonly rules?: NodeRules;
|
|
484
|
+
readonly extensions?: Extension[];
|
|
485
|
+
readonly keymap?: KeyBinding[];
|
|
486
|
+
readonly toolbar?: ToolbarContribution[];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
interface ComposedExtension {
|
|
490
|
+
extensions: Extension[];
|
|
491
|
+
toolbar: ToolbarContribution[];
|
|
492
|
+
}
|
|
493
|
+
declare function composeExtensions(modules: readonly MarkdownExtension[]): ComposedExtension;
|
|
494
|
+
|
|
495
|
+
declare const highlightExtension: MarkdownExtension;
|
|
496
|
+
|
|
497
|
+
declare const footnoteExtension: MarkdownExtension;
|
|
498
|
+
|
|
499
|
+
declare const mathExtension: MarkdownExtension;
|
|
500
|
+
|
|
501
|
+
declare const mermaidExtension: MarkdownExtension;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* A FACTORY, not a const: each composition gets its own editing surface (the
|
|
505
|
+
* one-active-edit state), so two mounted editors can never share a cell edit.
|
|
506
|
+
*/
|
|
507
|
+
declare function tableExtension(): MarkdownExtension;
|
|
508
|
+
|
|
509
|
+
declare const wikilinkExtension: MarkdownExtension;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Mermaid rendering, decoupled from CodeMirror — shared by the editor widget,
|
|
513
|
+
* the document export (which ships the SVG to the backend), and the clipboard
|
|
514
|
+
* (which needs a PNG, since Google Docs/Word drop SVG on paste).
|
|
515
|
+
*
|
|
516
|
+
* The mermaid library is heavy (~2 MB minified), so it loads lazily on the
|
|
517
|
+
* first diagram — bundled with the app, never fetched at runtime, so diagrams
|
|
518
|
+
* work fully offline. Render results are cached by source text; PNGs are
|
|
519
|
+
* rasterised once per diagram in the background (see {@link warmMermaidPng})
|
|
520
|
+
* so the SYNCHRONOUS copy event can embed them from cache.
|
|
521
|
+
*/
|
|
522
|
+
type MermaidRenderResult = {
|
|
523
|
+
ok: true;
|
|
524
|
+
svg: string;
|
|
525
|
+
} | {
|
|
526
|
+
ok: false;
|
|
527
|
+
message: string;
|
|
528
|
+
};
|
|
529
|
+
/** Whether a fence info string denotes a rendered mermaid diagram — the tag
|
|
530
|
+
* alone, any casing, no trailing meta ("mermaid title=x" is source, not a
|
|
531
|
+
* diagram). The one definition shared by the editor plugin, the export's SVG
|
|
532
|
+
* collector, and the clipboard, so every surface classifies identically. */
|
|
533
|
+
declare function isMermaidFenceInfo(info: string): boolean;
|
|
534
|
+
/** Render a mermaid diagram source to SVG, memoised by source. Never rejects —
|
|
535
|
+
* a parse error resolves to `{ ok: false, message }`. */
|
|
536
|
+
declare function renderMermaidToSvg(source: string): Promise<MermaidRenderResult>;
|
|
537
|
+
/** The cached PNG data URI for a diagram source, if it has been rasterised. */
|
|
538
|
+
declare function getCachedMermaidPng(source: string): string | null;
|
|
539
|
+
/** Render + rasterise a diagram in the background so a LATER (synchronous)
|
|
540
|
+
* clipboard copy can embed it from cache. Fire-and-forget: failures leave the
|
|
541
|
+
* cache empty and the copy degrades to the source block. */
|
|
542
|
+
declare function warmMermaidPng(source: string): Promise<void>;
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Synchronous fence highlighting for the clipboard (#149).
|
|
546
|
+
*
|
|
547
|
+
* A copy event writes `text/html` synchronously, so highlighting must not
|
|
548
|
+
* await anything. The trick: the grammars are the SAME `@codemirror/language-data`
|
|
549
|
+
* singletons the editor's nested fence parse loads — any fence visible in the
|
|
550
|
+
* editor has its grammar warm, so highlighting the copied code is a sync parse.
|
|
551
|
+
* A cold grammar (copy from a doc whose fence never rendered) kicks the load
|
|
552
|
+
* and returns null: THIS copy ships plain, the next one is highlighted.
|
|
553
|
+
*
|
|
554
|
+
* Output is inline-styled spans (the palette from
|
|
555
|
+
* [codePalette](./codePalette.ts), same colors as the editor) — pasted HTML
|
|
556
|
+
* carries no stylesheet, so classes would be dead weight in Docs/Word.
|
|
557
|
+
*/
|
|
558
|
+
interface HighlightedSpan {
|
|
559
|
+
text: string;
|
|
560
|
+
/** Inline CSS for the span; absent for unstyled text (incl. line breaks). */
|
|
561
|
+
style?: string;
|
|
562
|
+
}
|
|
563
|
+
/** Highlight `code` as `lang` into inline-styled spans, or null when no
|
|
564
|
+
* grammar matches or the grammar isn't loaded yet (the load is kicked so a
|
|
565
|
+
* later copy succeeds). Never throws — a parser hiccup falls back to null. */
|
|
566
|
+
declare function highlightFenceSpans(lang: string, code: string): HighlightedSpan[] | null;
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Image-insertion pipeline. Used by both paste-from-clipboard and
|
|
570
|
+
* drag-and-drop. Both produce a `Blob`; this module turns the blob
|
|
571
|
+
* into a markdown-reference-able path:
|
|
572
|
+
*
|
|
573
|
+
* * When a `saveBytes` writer is injected (a desktop host): writes
|
|
574
|
+
* the bytes to `images/<timestamp>-<hash>.<ext>` and returns the
|
|
575
|
+
* relative path. The markdown reference is portable across
|
|
576
|
+
* machines because it's a real on-disk file.
|
|
577
|
+
* * Otherwise (plain browser, or the write fails): falls back to a
|
|
578
|
+
* data URL embedded directly in the markdown. The markdown file
|
|
579
|
+
* bloats but the image stays visible through page reload without
|
|
580
|
+
* needing a binary file API.
|
|
581
|
+
*
|
|
582
|
+
* The writer is injected rather than imported so this module stays
|
|
583
|
+
* environment-agnostic — the editor reads it off a CM6 facet
|
|
584
|
+
* ({@link saveImageBytesFacet}) the host wires up.
|
|
585
|
+
*
|
|
586
|
+
* Returned `markdownReference` is what the caller drops into
|
|
587
|
+
* ``. `warning` is set in the data-URL fallback case so
|
|
588
|
+
* the caller can surface a note to the user.
|
|
589
|
+
*/
|
|
590
|
+
interface ImageInsertOptions {
|
|
591
|
+
blob: Blob;
|
|
592
|
+
/**
|
|
593
|
+
* Persist the bytes at a workspace-relative path. `null`/omitted ⇒
|
|
594
|
+
* inline as a data URL (browser fallback).
|
|
595
|
+
*/
|
|
596
|
+
saveBytes?: ((relPath: string, bytes: Uint8Array) => Promise<void>) | null;
|
|
597
|
+
/**
|
|
598
|
+
* Alt text the user typed (currently always synthesized as
|
|
599
|
+
* "pasted-image" or "dropped-image"; in phase 3d the inline
|
|
600
|
+
* rename UI lets the user edit it post-insert).
|
|
601
|
+
*/
|
|
602
|
+
alt?: string;
|
|
603
|
+
}
|
|
604
|
+
interface ImageInsertResult {
|
|
605
|
+
markdownReference: string;
|
|
606
|
+
alt: string;
|
|
607
|
+
warning?: string;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Save the blob and return the markdown reference. Errors are
|
|
611
|
+
* structured (the caller decides whether to retry with a fallback
|
|
612
|
+
* or surface a notification).
|
|
613
|
+
*/
|
|
614
|
+
declare function insertImageBlob(opts: ImageInsertOptions): Promise<ImageInsertResult>;
|
|
615
|
+
/**
|
|
616
|
+
* Build a markdown image insertion string from the result. Keeps
|
|
617
|
+
* the call site (handlePaste / handleDrop) tidy.
|
|
618
|
+
*/
|
|
619
|
+
declare function buildImageMarkdown(result: ImageInsertResult): string;
|
|
620
|
+
/**
|
|
621
|
+
* Scan a DataTransferItemList for image blobs. Returns every
|
|
622
|
+
* image found, in order. Empty array when nothing matches —
|
|
623
|
+
* caller treats that as "fall through to text paste".
|
|
624
|
+
*/
|
|
625
|
+
declare function extractImageBlobs(items: DataTransferItemList | null | undefined): Blob[];
|
|
626
|
+
/**
|
|
627
|
+
* Scan a FileList (drag-and-drop's `dataTransfer.files`) for
|
|
628
|
+
* image files. Drag-drop and paste use different shapes; this
|
|
629
|
+
* keeps the two paths symmetrical at the call site.
|
|
630
|
+
*/
|
|
631
|
+
declare function extractImageFiles(files: FileList | null | undefined): File[];
|
|
632
|
+
|
|
633
|
+
declare const imageInsertHandlers: _codemirror_state.Extension;
|
|
634
|
+
/**
|
|
635
|
+
* Open the browser's native file picker, then run the picked files
|
|
636
|
+
* through the same insert pipeline. Called by the toolbar's Image
|
|
637
|
+
* button.
|
|
638
|
+
*/
|
|
639
|
+
declare function pickImageFileForCaret(view: EditorView): void;
|
|
640
|
+
|
|
641
|
+
interface ShowMenuArgs {
|
|
642
|
+
x: number;
|
|
643
|
+
y: number;
|
|
644
|
+
view: EditorView;
|
|
645
|
+
alt: string;
|
|
646
|
+
rawSrc: string;
|
|
647
|
+
sourceFrom: number;
|
|
648
|
+
sourceTo: number;
|
|
649
|
+
}
|
|
650
|
+
declare function showImageActionMenu(args: ShowMenuArgs): void;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The "edit this image's alt text" custom-event contract — a plain constant and
|
|
654
|
+
* type with NO runtime CodeMirror dependency (the `EditorView` reference is
|
|
655
|
+
* `import type`, erased at build). The lazy-loaded editor dispatches this event;
|
|
656
|
+
* the host shell listens for it. Keeping the contract here means a host can
|
|
657
|
+
* import the event name without pulling the whole editor into its initial
|
|
658
|
+
* bundle (it previously came from `imageActionMenu`, which imports CodeMirror).
|
|
659
|
+
*/
|
|
660
|
+
declare const IMAGE_EDIT_ALT_EVENT = "ai-editor:edit-image-alt";
|
|
661
|
+
interface ImageEditAltEventDetail {
|
|
662
|
+
view: EditorView;
|
|
663
|
+
sourceFrom: number;
|
|
664
|
+
sourceTo: number;
|
|
665
|
+
currentAlt: string;
|
|
666
|
+
rawSrc: string;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* YAML frontmatter parsing + serialization.
|
|
671
|
+
*
|
|
672
|
+
* Convention: `--- yaml ---` block at the very top of a markdown
|
|
673
|
+
* file (before any other content). Format:
|
|
674
|
+
*
|
|
675
|
+
* ---
|
|
676
|
+
* status: draft
|
|
677
|
+
* tags:
|
|
678
|
+
* - application
|
|
679
|
+
* - fellowship
|
|
680
|
+
* ---
|
|
681
|
+
*
|
|
682
|
+
* # Real content starts here
|
|
683
|
+
*
|
|
684
|
+
* This module separates the YAML chunk from the prose chunk so
|
|
685
|
+
* the WYSIWYG editor can render only the prose (no raw `key:
|
|
686
|
+
* value` lines bleeding into the user's writing surface) while a
|
|
687
|
+
* separate properties UI shows / edits the frontmatter.
|
|
688
|
+
*
|
|
689
|
+
* Round-trip rules:
|
|
690
|
+
* - If a file has no frontmatter on disk, we MUST NOT add one
|
|
691
|
+
* on save just because the properties panel exists.
|
|
692
|
+
* - If a file has frontmatter, we serialize it using the same
|
|
693
|
+
* keys/order the user wrote (best effort — yaml lib preserves
|
|
694
|
+
* scalar formatting but not always blank lines / comments).
|
|
695
|
+
* - The fence marker is always exactly `---` on its own line.
|
|
696
|
+
* Some Obsidian users prefer `+++` (TOML) — out of scope for
|
|
697
|
+
* v1; we round-trip those as body content unchanged.
|
|
698
|
+
*/
|
|
699
|
+
/**
|
|
700
|
+
* The frontmatter value type. Each entry is whatever YAML can
|
|
701
|
+
* decode to — strings, numbers, booleans, arrays, nested objects.
|
|
702
|
+
* The properties UI handles primitives + flat arrays cleanly;
|
|
703
|
+
* nested objects fall back to a raw YAML editor.
|
|
704
|
+
*/
|
|
705
|
+
type FrontmatterValue = string | number | boolean | null | FrontmatterValue[] | {
|
|
706
|
+
[key: string]: FrontmatterValue;
|
|
707
|
+
};
|
|
708
|
+
type Frontmatter = Record<string, FrontmatterValue>;
|
|
709
|
+
interface MarkdownDocument {
|
|
710
|
+
/** Parsed YAML key/values. `null` means "no frontmatter block present". */
|
|
711
|
+
frontmatter: Frontmatter | null;
|
|
712
|
+
/** The prose portion — everything after the closing `---`. */
|
|
713
|
+
body: string;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Split a markdown string into its frontmatter + body. Tolerant
|
|
717
|
+
* of:
|
|
718
|
+
* - No frontmatter at all → `frontmatter: null, body: input`
|
|
719
|
+
* - Malformed YAML → `frontmatter: null, body: input` (we treat
|
|
720
|
+
* the whole thing as prose rather than show parse errors)
|
|
721
|
+
* - Empty frontmatter block (`---\n---`) → `frontmatter: {}`
|
|
722
|
+
*/
|
|
723
|
+
declare function parseFrontmatter(markdown: string): MarkdownDocument;
|
|
724
|
+
/**
|
|
725
|
+
* Recombine frontmatter + body into a markdown string. Inverse
|
|
726
|
+
* of `parseFrontmatter` for legal inputs.
|
|
727
|
+
*
|
|
728
|
+
* - `frontmatter: null` → just the body, no `---` fences added
|
|
729
|
+
* - `frontmatter: {}` → no fences either (an empty frontmatter
|
|
730
|
+
* block is semantically the same as "no frontmatter" and we
|
|
731
|
+
* prefer the cleaner shape on save)
|
|
732
|
+
* - Otherwise → `---\n<yaml>\n---\n<body>`
|
|
733
|
+
*/
|
|
734
|
+
declare function serializeMarkdown(doc: MarkdownDocument): string;
|
|
735
|
+
/**
|
|
736
|
+
* Helper: update a single frontmatter field in a markdown string
|
|
737
|
+
* without touching the body. Used by the Properties UI when the
|
|
738
|
+
* user edits one value at a time.
|
|
739
|
+
*
|
|
740
|
+
* If the file had no frontmatter, this adds one with just the
|
|
741
|
+
* one field. If the value is `null` and the key exists, removes
|
|
742
|
+
* the key (and removes the whole frontmatter block if it becomes
|
|
743
|
+
* empty).
|
|
744
|
+
*/
|
|
745
|
+
declare function setFrontmatterField(markdown: string, key: string, value: FrontmatterValue | null): string;
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Wikilink (`[[target]]` / `[[target|label]]`) parsing + target resolution.
|
|
749
|
+
*
|
|
750
|
+
* The canonical rule lives in the `workspace-index` crate
|
|
751
|
+
* (`wikilink_target_and_label` / `resolve_document_target` / `path_stem_matches`
|
|
752
|
+
* / `slug_key`) — that's what builds the backlink graph. This is the
|
|
753
|
+
* **client-side mirror** used to navigate a *clicked* wikilink, so the editor
|
|
754
|
+
* and chat agree with the sidebar's backlinks. Keep the two in sync; if the
|
|
755
|
+
* crate's rule changes, change it here too.
|
|
756
|
+
*
|
|
757
|
+
* Difference from the crate: an unresolved target returns `null` (we only
|
|
758
|
+
* navigate to files that exist), whereas the crate keeps a would-be path for
|
|
759
|
+
* the graph edge.
|
|
760
|
+
*/
|
|
761
|
+
/** Split a wikilink body into its target and display label. */
|
|
762
|
+
declare function parseWikilinkBody(body: string): {
|
|
763
|
+
target: string;
|
|
764
|
+
label: string;
|
|
765
|
+
};
|
|
766
|
+
/** Resolve a wikilink target to an existing workspace file path, or `null`. */
|
|
767
|
+
declare function resolveWikilinkTarget(rawTarget: string, options: {
|
|
768
|
+
fromPath?: string;
|
|
769
|
+
knownPaths: ReadonlySet<string>;
|
|
770
|
+
}): string | null;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Resolve a markdown link href to a navigation target.
|
|
774
|
+
*
|
|
775
|
+
* A link in a document (or an agent's chat reply) is either:
|
|
776
|
+
* - **external** — anything with a URI scheme (`https:`, `mailto:`, …) or a
|
|
777
|
+
* protocol-relative `//host`; opened in the browser, and
|
|
778
|
+
* - **internal** — a workspace-relative path that resolves to a file the
|
|
779
|
+
* workspace actually contains; opened in a tab.
|
|
780
|
+
*
|
|
781
|
+
* This is pure path resolution (normalize `.`/`..`, reject escaping the vault
|
|
782
|
+
* root, membership-check against the known files) — it is **not** the index's
|
|
783
|
+
* link parser. The index already resolves links into `graphEdges`; this just
|
|
784
|
+
* decides where a *clicked* href points so the UI can navigate. An href that
|
|
785
|
+
* looks internal but matches no known file returns `null` (a broken link, not
|
|
786
|
+
* something to navigate to).
|
|
787
|
+
*/
|
|
788
|
+
type ResolvedWorkspaceLink = {
|
|
789
|
+
kind: "internal";
|
|
790
|
+
path: string;
|
|
791
|
+
} | {
|
|
792
|
+
kind: "external";
|
|
793
|
+
href: string;
|
|
794
|
+
};
|
|
795
|
+
interface ResolveWorkspaceLinkOptions {
|
|
796
|
+
/** Workspace-relative path of the document the link lives in. Relative hrefs
|
|
797
|
+
* resolve against this file's directory. Omitted ⇒ resolve from the vault
|
|
798
|
+
* root (used for agent chat replies, which emit root-relative paths). */
|
|
799
|
+
fromPath?: string;
|
|
800
|
+
/** Every workspace-relative file path, for membership checks. */
|
|
801
|
+
knownPaths: ReadonlySet<string>;
|
|
802
|
+
}
|
|
803
|
+
declare function resolveWorkspaceLink(href: string, options: ResolveWorkspaceLinkOptions): ResolvedWorkspaceLink | null;
|
|
804
|
+
|
|
805
|
+
export { type CaretContextSnapshot, type CodeMirrorEditorMode, CodeMirrorMarkdownEditor, type CodeMirrorMarkdownEditorProps, type ComposedExtension, type DocumentTextChange, type EditorSelectionSnapshot, type Frontmatter, type FrontmatterValue, type HighlightedSpan, IMAGE_EDIT_ALT_EVENT, type ImageEditAltEventDetail, type ImageInsertOptions, type ImageInsertResult, type ImageResolveContext, type MarkdownDocument, type MarkdownExtension, type MermaidRenderResult, type NodeContext, type NodeRule, type NodeRules, type OpenExternalUrl, type Paint, type ResolveImageSrc, type ResolveWorkspaceLinkOptions, type ResolvedWorkspaceLink, type SaveImageBytes, type SourceRange, type ToolbarContribution, blockCommands, buildImageMarkdown, composeExtensions, computeFileDir, defaultResolveImageSrc, dirnamePath, editorBaseTheme, extractImageBlobs, extractImageFiles, footnoteExtension, formatCommands, getCachedMermaidPng, hasUriScheme, headingLine, hideAlways, highlightExtension, highlightFenceSpans, imageInsertHandlers, insertImageBlob, isAbsolutePath, isMermaidFenceInfo, joinPath, line, mark, markdownDecorationsPlugin, mathExtension, mermaidExtension, nodeRulesFacet, onEditorUpdate, parseFrontmatter, parseWikilinkBody, pickImageFileForCaret, raw, renderMermaidToSvg, resolveWikilinkTarget, resolveWorkspaceLink, serializeMarkdown, setFrontmatterField, showImageActionMenu, structural, tableExtension, treeAt, warmMermaidPng, wikilinkExtension };
|