@liminis/editor 0.1.0-rc.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 +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- package/package.json +175 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified annotation types (ADR-077).
|
|
3
|
+
*
|
|
4
|
+
* An annotation is a range-anchored marker over document text. Comments and
|
|
5
|
+
* corrections are not separate features here — they are two *kinds*, differing
|
|
6
|
+
* only by the {@link AnnotationKindConfig} a host supplies. Everything in this
|
|
7
|
+
* module is DOM-free, Lexical-free and React-free so it can be reached from the
|
|
8
|
+
* `./annotations` subpath and used outside a rendered editor.
|
|
9
|
+
*
|
|
10
|
+
* Per ADR-075 the seam is persistence: the package owns anchor mechanics and
|
|
11
|
+
* marker rendering; identity, storage and lifecycle (resolve/reopen for
|
|
12
|
+
* comments, accept/reject for corrections) stay in the consuming app. The
|
|
13
|
+
* package treats `id` as opaque and `payload` as pass-through.
|
|
14
|
+
*/
|
|
15
|
+
import type { Anchor, AnchorOutcome, AnchorRange } from './anchor-model.js';
|
|
16
|
+
/**
|
|
17
|
+
* Which kind of annotation this is. Deliberately a plain string rather than a
|
|
18
|
+
* closed union — a host may configure kinds the package has never heard of,
|
|
19
|
+
* and the two Liminis/Zusammen kinds carry no special status in the mechanism.
|
|
20
|
+
*/
|
|
21
|
+
export type AnnotationKind = string;
|
|
22
|
+
/** Presentation hints a host may attach per annotation, overriding kind defaults. */
|
|
23
|
+
export interface AnnotationPresentation {
|
|
24
|
+
/** Extra CSS class applied to this annotation's marker element. */
|
|
25
|
+
className?: string;
|
|
26
|
+
/** Accessible label for the marker; falls back to the kind name. */
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A range-anchored marker over document text. `id` is opaque to the package —
|
|
31
|
+
* hosts mint and persist it. `payload` is carried through untouched and handed
|
|
32
|
+
* back on activation, so a host can round-trip its own domain object without
|
|
33
|
+
* the package understanding it.
|
|
34
|
+
*/
|
|
35
|
+
export interface Annotation<TPayload = unknown> {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: AnnotationKind;
|
|
38
|
+
anchor: Anchor;
|
|
39
|
+
/**
|
|
40
|
+
* The resolution outcome currently in effect for this anchor. Hosts compute
|
|
41
|
+
* this (via `resolveAnchors` or their own store) — the package never
|
|
42
|
+
* classifies on the host's behalf. Defaults to `unchanged` when omitted.
|
|
43
|
+
*/
|
|
44
|
+
outcome?: AnchorOutcome;
|
|
45
|
+
presentation?: AnnotationPresentation;
|
|
46
|
+
payload?: TPayload;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* How a kind's markers paint. `none` places no visible marker at all, which is
|
|
50
|
+
* what corrections use: they adopt the anchor model and the capture primitive
|
|
51
|
+
* but render exactly what they render today — nothing (FR-006 parity).
|
|
52
|
+
*/
|
|
53
|
+
export type AnnotationMarkerStyle = 'highlight' | 'squiggle' | 'none';
|
|
54
|
+
/** Where a kind's user-initiated create affordance is offered, if anywhere. */
|
|
55
|
+
export interface AnnotationCreateAffordance {
|
|
56
|
+
surface: 'toolbar' | 'contextMenu';
|
|
57
|
+
label?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The per-kind configuration record. SC-001's claim rests on this type: a
|
|
61
|
+
* reviewer can point at two values of it as the entire difference between the
|
|
62
|
+
* comment feature and the correction feature.
|
|
63
|
+
*/
|
|
64
|
+
export interface AnnotationKindConfig<TPayload = unknown> {
|
|
65
|
+
markerStyle: AnnotationMarkerStyle;
|
|
66
|
+
/**
|
|
67
|
+
* User-initiated creation. Omitted or null means host-injected only — the
|
|
68
|
+
* package offers no way for a user to create one.
|
|
69
|
+
*
|
|
70
|
+
* CAUTION: a kind literally named `correction` on the `contextMenu` surface
|
|
71
|
+
* is special-cased internally — `SelectionContextMenuPlugin` opens the
|
|
72
|
+
* package's own legacy correction panel for it in addition to dispatching
|
|
73
|
+
* this create flow. If such a kind's host also supplies
|
|
74
|
+
* `onCreateAnnotation`, both UIs open off the same click with no
|
|
75
|
+
* coordination between them. Name a kind something other than `correction`
|
|
76
|
+
* if you want only the standard create flow.
|
|
77
|
+
*/
|
|
78
|
+
createAffordance?: AnnotationCreateAffordance | null;
|
|
79
|
+
/**
|
|
80
|
+
* Whether this annotation gets a live `MarkNode` in the document. Defaults to
|
|
81
|
+
* {@link shouldPlaceLiveMark} over the annotation's outcome when omitted.
|
|
82
|
+
*
|
|
83
|
+
* Consulted only when `markerStyle` is not `none` — see
|
|
84
|
+
* {@link deriveMarkerTargets} for why that precedence exists.
|
|
85
|
+
*/
|
|
86
|
+
livemarkPolicy?: (annotation: Annotation<TPayload>) => boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Whether the capture primitive leaves its transient mark in place after
|
|
89
|
+
* capturing an anchor. Comments retain it (the composer highlights the
|
|
90
|
+
* passage); corrections discard it.
|
|
91
|
+
*
|
|
92
|
+
* The discard is *not* part of the wrapping update. `AnnotationPlugin` wraps
|
|
93
|
+
* inside the command handler, then reads the anchor back and removes the
|
|
94
|
+
* mark on a `queueMicrotask` — the read cannot be inline, because a command
|
|
95
|
+
* handler runs inside an active Lexical update and the nested wrap is not
|
|
96
|
+
* observable until that update flushes. The guarantee is therefore "removed
|
|
97
|
+
* before the browser paints" (microtasks drain ahead of paint), not "removed
|
|
98
|
+
* in the same update". Anything that pushes the discard past a task boundary
|
|
99
|
+
* — an `await`, a `setTimeout`, a `requestAnimationFrame` — would let the
|
|
100
|
+
* mark flash visibly first.
|
|
101
|
+
*/
|
|
102
|
+
retainMarkOnCreate?: boolean;
|
|
103
|
+
}
|
|
104
|
+
/** A host's full annotation configuration: kind name → its config. */
|
|
105
|
+
export type AnnotationKindConfigs = Record<AnnotationKind, AnnotationKindConfig>;
|
|
106
|
+
/**
|
|
107
|
+
* A resolved instruction to place one marker. Ported from Zusammen's
|
|
108
|
+
* `comment-thread.ts`; its `deriveMarkerTargets` is deliberately *not* ported —
|
|
109
|
+
* that function takes Zusammen's `Thread`/`Comment`, which are app-domain under
|
|
110
|
+
* ADR-075. The package accepts already-derived annotations instead.
|
|
111
|
+
*/
|
|
112
|
+
export interface MarkerTarget {
|
|
113
|
+
annotationId: string;
|
|
114
|
+
kind: AnnotationKind;
|
|
115
|
+
anchor: Anchor;
|
|
116
|
+
outcome: AnchorOutcome;
|
|
117
|
+
/**
|
|
118
|
+
* Carried through from the source annotation. The marker renderer sees only
|
|
119
|
+
* targets, so without this the per-annotation className/label overrides
|
|
120
|
+
* {@link AnnotationPresentation} promises would be silently unreachable.
|
|
121
|
+
*/
|
|
122
|
+
presentation?: AnnotationPresentation;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Whether `outcome` gets a live in-document `MarkNode` at all. Only
|
|
126
|
+
* `unchanged`/`re-attached` do — `flagged`'s match is uncertain and `orphaned`
|
|
127
|
+
* has nothing left to point at, so both stay panel-only, never placed on text
|
|
128
|
+
* the resolver wasn't confident about.
|
|
129
|
+
*/
|
|
130
|
+
export declare function shouldPlaceLiveMark(outcome: AnchorOutcome): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Derive marker targets from host-supplied annotations, applying each kind's
|
|
133
|
+
* live-mark policy. Annotations whose `kind` has no configuration are dropped;
|
|
134
|
+
* the caller is responsible for warning about them (the logger is injected at
|
|
135
|
+
* the React layer, and this module stays dependency-free).
|
|
136
|
+
*
|
|
137
|
+
* `markerStyle: 'none'` takes precedence over `livemarkPolicy` and yields no
|
|
138
|
+
* target at all. A target is not merely a styling instruction — it is what
|
|
139
|
+
* makes `AnnotationMarkPlacementPlugin` wrap the text in a real `MarkNode`,
|
|
140
|
+
* which reaches the DOM as a `<mark>` element. `AnnotationMarkerPlugin` skips
|
|
141
|
+
* decoration for `none`, so that element would carry no `annotation-mark-*`
|
|
142
|
+
* class, and `styles.css` neutralizes the UA's default yellow `<mark>`
|
|
143
|
+
* background only under those classes — leaving a persistent uncontrolled
|
|
144
|
+
* highlight, the exact opposite of what `none` promises. Gating here rather
|
|
145
|
+
* than at paint time keeps the promise structural instead of leaving it to a
|
|
146
|
+
* host remembering to pair `none` with `livemarkPolicy: () => false`.
|
|
147
|
+
*/
|
|
148
|
+
export declare function deriveMarkerTargets(annotations: readonly Annotation[], kinds: AnnotationKindConfigs): MarkerTarget[];
|
|
149
|
+
/**
|
|
150
|
+
* The imperative bridge from a host to the live marks inside a mounted editor —
|
|
151
|
+
* set once the editor mounts, read by the host at delete-time and
|
|
152
|
+
* checkpoint-time. Ported from Zusammen's `comment-editor-handle.ts`.
|
|
153
|
+
*/
|
|
154
|
+
export interface AnnotationEditorHandle {
|
|
155
|
+
/** Removes every live mark wrapping `annotationId` (annotation deleted, or composer cancelled). */
|
|
156
|
+
removeMarksForAnnotation: (annotationId: string) => void;
|
|
157
|
+
/** The current live range of every marked passage, keyed by annotation id. */
|
|
158
|
+
collectLiveAnchorSnapshots: (markdownText: string) => Map<string, AnchorRange>;
|
|
159
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `outcome` gets a live in-document `MarkNode` at all. Only
|
|
3
|
+
* `unchanged`/`re-attached` do — `flagged`'s match is uncertain and `orphaned`
|
|
4
|
+
* has nothing left to point at, so both stay panel-only, never placed on text
|
|
5
|
+
* the resolver wasn't confident about.
|
|
6
|
+
*/
|
|
7
|
+
export function shouldPlaceLiveMark(outcome) {
|
|
8
|
+
return outcome === 'unchanged' || outcome === 're-attached';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Derive marker targets from host-supplied annotations, applying each kind's
|
|
12
|
+
* live-mark policy. Annotations whose `kind` has no configuration are dropped;
|
|
13
|
+
* the caller is responsible for warning about them (the logger is injected at
|
|
14
|
+
* the React layer, and this module stays dependency-free).
|
|
15
|
+
*
|
|
16
|
+
* `markerStyle: 'none'` takes precedence over `livemarkPolicy` and yields no
|
|
17
|
+
* target at all. A target is not merely a styling instruction — it is what
|
|
18
|
+
* makes `AnnotationMarkPlacementPlugin` wrap the text in a real `MarkNode`,
|
|
19
|
+
* which reaches the DOM as a `<mark>` element. `AnnotationMarkerPlugin` skips
|
|
20
|
+
* decoration for `none`, so that element would carry no `annotation-mark-*`
|
|
21
|
+
* class, and `styles.css` neutralizes the UA's default yellow `<mark>`
|
|
22
|
+
* background only under those classes — leaving a persistent uncontrolled
|
|
23
|
+
* highlight, the exact opposite of what `none` promises. Gating here rather
|
|
24
|
+
* than at paint time keeps the promise structural instead of leaving it to a
|
|
25
|
+
* host remembering to pair `none` with `livemarkPolicy: () => false`.
|
|
26
|
+
*/
|
|
27
|
+
export function deriveMarkerTargets(annotations, kinds) {
|
|
28
|
+
const targets = [];
|
|
29
|
+
for (const annotation of annotations) {
|
|
30
|
+
const config = kinds[annotation.kind];
|
|
31
|
+
if (!config)
|
|
32
|
+
continue;
|
|
33
|
+
if (config.markerStyle === 'none')
|
|
34
|
+
continue;
|
|
35
|
+
const outcome = annotation.outcome ?? 'unchanged';
|
|
36
|
+
const placeLive = config.livemarkPolicy
|
|
37
|
+
? config.livemarkPolicy(annotation)
|
|
38
|
+
: shouldPlaceLiveMark(outcome);
|
|
39
|
+
if (!placeLive)
|
|
40
|
+
continue;
|
|
41
|
+
targets.push({
|
|
42
|
+
annotationId: annotation.id,
|
|
43
|
+
kind: annotation.kind,
|
|
44
|
+
anchor: annotation.anchor,
|
|
45
|
+
outcome,
|
|
46
|
+
presentation: annotation.presentation,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return targets;
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@liminis/editor/annotations` — the headless annotation surface (ADR-077).
|
|
3
|
+
*
|
|
4
|
+
* The sixth declared export subpath, added as a *considered* export under
|
|
5
|
+
* ADR-075's five-subpath rule. It exists because this surface has a profile no
|
|
6
|
+
* existing subpath has: it must be callable outside a rendered editor, so it
|
|
7
|
+
* is free of DOM, React and Lexical — but it is not pure-mdast either (the
|
|
8
|
+
* anchor schema imports `zod`), so it does not belong on `./markdown`, and it
|
|
9
|
+
* must not go on `./headless`, whose `mathjax-config` re-export drags ~1.9 MB
|
|
10
|
+
* of side-effectful imports.
|
|
11
|
+
*
|
|
12
|
+
* Everything reachable from here is safe to call from a main process, a worker,
|
|
13
|
+
* or a test with no DOM. The React plugins and the marker-placement code live
|
|
14
|
+
* on the root barrel alongside `<Editor>` instead.
|
|
15
|
+
*/
|
|
16
|
+
export { ANCHOR_SCHEMA, ANCHOR_RESOLUTION_SCHEMA, CONTEXT_WINDOW_CHARS, captureAnchor, } from './annotations/anchor-model.js';
|
|
17
|
+
export type { Anchor, AnchorFields, AnchorRange, AnchorOutcome, AnchorResolution, } from './annotations/anchor-model.js';
|
|
18
|
+
export { resolveAnchor, resolveAnchors, similarity, noopProposeSemanticRelocation, REATTACH_THRESHOLD, FLAG_THRESHOLD, } from './annotations/anchor-resolver.js';
|
|
19
|
+
export type { ProposeSemanticRelocation, ResolveAnchorOptions, IdentifiedAnchor, AnchorResolutionResult, } from './annotations/anchor-resolver.js';
|
|
20
|
+
export { parseBlocks, findEnclosingBlock, blockPlainText, sentenceChunks, } from './annotations/block-structure.js';
|
|
21
|
+
export type { Block, BlockType, SentenceChunk } from './annotations/block-structure.js';
|
|
22
|
+
export { locateInSpan } from './annotations/anchor-align.js';
|
|
23
|
+
export type { SpanRange, LocateInSpanOptions } from './annotations/anchor-align.js';
|
|
24
|
+
export { shouldPlaceLiveMark, deriveMarkerTargets } from './annotations/types.js';
|
|
25
|
+
export type { Annotation, AnnotationKind, AnnotationKindConfig, AnnotationKindConfigs, AnnotationCreateAffordance, AnnotationMarkerStyle, AnnotationPresentation, AnnotationEditorHandle, MarkerTarget, } from './annotations/types.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@liminis/editor/annotations` — the headless annotation surface (ADR-077).
|
|
3
|
+
*
|
|
4
|
+
* The sixth declared export subpath, added as a *considered* export under
|
|
5
|
+
* ADR-075's five-subpath rule. It exists because this surface has a profile no
|
|
6
|
+
* existing subpath has: it must be callable outside a rendered editor, so it
|
|
7
|
+
* is free of DOM, React and Lexical — but it is not pure-mdast either (the
|
|
8
|
+
* anchor schema imports `zod`), so it does not belong on `./markdown`, and it
|
|
9
|
+
* must not go on `./headless`, whose `mathjax-config` re-export drags ~1.9 MB
|
|
10
|
+
* of side-effectful imports.
|
|
11
|
+
*
|
|
12
|
+
* Everything reachable from here is safe to call from a main process, a worker,
|
|
13
|
+
* or a test with no DOM. The React plugins and the marker-placement code live
|
|
14
|
+
* on the root barrel alongside `<Editor>` instead.
|
|
15
|
+
*/
|
|
16
|
+
// The durable anchor model — capture, schemas, and the outcome vocabulary.
|
|
17
|
+
export { ANCHOR_SCHEMA, ANCHOR_RESOLUTION_SCHEMA, CONTEXT_WINDOW_CHARS, captureAnchor, } from './annotations/anchor-model.js';
|
|
18
|
+
// The sole fuzzy-matching module (FR-003). `resolveAnchors` is the batch entry
|
|
19
|
+
// point hosts call; it is async because the relocation seam may be host-supplied.
|
|
20
|
+
export { resolveAnchor, resolveAnchors, similarity, noopProposeSemanticRelocation, REATTACH_THRESHOLD, FLAG_THRESHOLD, } from './annotations/anchor-resolver.js';
|
|
21
|
+
// Block structure — the structural context anchors are captured against.
|
|
22
|
+
export { parseBlocks, findEnclosingBlock, blockPlainText, sentenceChunks, } from './annotations/block-structure.js';
|
|
23
|
+
// Span alignment, used when mapping an anchor onto a live document offset span.
|
|
24
|
+
export { locateInSpan } from './annotations/anchor-align.js';
|
|
25
|
+
// The annotation primitive and its per-kind configuration — the sole difference
|
|
26
|
+
// between the comment feature and the correction feature (SC-001).
|
|
27
|
+
export { shouldPlaceLiveMark, deriveMarkerTargets } from './annotations/types.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
import type { SelectionContextMenuEvent } from './editor/SelectionContextMenuPlugin.js';
|
|
3
|
+
import type { SweepFn } from './editor/AmbientCorrectionPlugin.js';
|
|
4
|
+
import type { AnnotationCreateEvent } from './editor/AnnotationPlugin.js';
|
|
5
|
+
import type { Annotation, AnnotationEditorHandle, AnnotationKindConfigs } from '../annotations/types.js';
|
|
6
|
+
import type { WikiLinkPromotionMode } from './mapper/lexicalToMdast.js';
|
|
7
|
+
export interface CursorState {
|
|
8
|
+
offset: number;
|
|
9
|
+
contextBefore: string;
|
|
10
|
+
contextAfter: string;
|
|
11
|
+
}
|
|
12
|
+
interface AppProps {
|
|
13
|
+
/** When false, the editor is read-only */
|
|
14
|
+
editable?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the editor takes focus shortly after mounting. Defaults to
|
|
17
|
+
* `false`; see `Editor`'s own prop for when to opt in.
|
|
18
|
+
*/
|
|
19
|
+
autoFocus?: boolean;
|
|
20
|
+
/** Content to display - when provided, used instead of IPC */
|
|
21
|
+
content?: string;
|
|
22
|
+
/** Called when content changes */
|
|
23
|
+
onChange?: (content: string) => void;
|
|
24
|
+
/** Path to the file being edited (used for file-type-specific UI like .mdc) */
|
|
25
|
+
filePath?: string;
|
|
26
|
+
/** Resolve a workspace-relative file path to a data URL for display */
|
|
27
|
+
resolveLocalAsset?: (relativePath: string) => Promise<string | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Whether an untitled relative link is promoted to wiki-link syntax on
|
|
30
|
+
* export. Defaults to `'promote'`, today's only behavior. Unlike
|
|
31
|
+
* `imagePathResolution`, this is a direct prop rather than sourced from IPC
|
|
32
|
+
* `SlashMDSettings` — `App` also supports a non-IPC "inline" mode (`content`/
|
|
33
|
+
* `onChange` props), where `settings` stays null forever, and a
|
|
34
|
+
* settings-only path would be unreachable there (liminis#951).
|
|
35
|
+
*/
|
|
36
|
+
wikiLinkPromotion?: WikiLinkPromotionMode;
|
|
37
|
+
/** Called when user right-clicks a text selection and chooses a context menu action */
|
|
38
|
+
onSelectionContextMenu?: (event: SelectionContextMenuEvent) => void;
|
|
39
|
+
/** Called when a single-word substitution is detected after a debounce window. */
|
|
40
|
+
onSubstitutionDetected?: (oldTerm: string, newTerm: string) => void;
|
|
41
|
+
/** Ref populated with a sweep function by AmbientCorrectionPlugin when active. */
|
|
42
|
+
sweepRef?: MutableRefObject<SweepFn | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Annotation kind configuration (ADR-077), forwarded to <Editor>. Absent
|
|
45
|
+
* means the annotation mechanism stays off entirely.
|
|
46
|
+
*/
|
|
47
|
+
annotationKinds?: AnnotationKindConfigs;
|
|
48
|
+
annotations?: Annotation[];
|
|
49
|
+
activeAnnotationId?: string | null;
|
|
50
|
+
scrollToAnnotation?: {
|
|
51
|
+
id: string;
|
|
52
|
+
nonce: number;
|
|
53
|
+
} | null;
|
|
54
|
+
onCreateAnnotation?: (event: AnnotationCreateEvent) => void;
|
|
55
|
+
onActivateAnnotation?: (id: string) => void;
|
|
56
|
+
annotationEditorHandleRef?: MutableRefObject<AnnotationEditorHandle | null>;
|
|
57
|
+
annotationLogger?: {
|
|
58
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
59
|
+
};
|
|
60
|
+
/** Override className on the root div (default: "min-h-screen p-0"). */
|
|
61
|
+
className?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function App({ editable, autoFocus, content: propContent, onChange: propOnChange, filePath, resolveLocalAsset, wikiLinkPromotion, onSelectionContextMenu, onSubstitutionDetected, sweepRef, annotationKinds, annotations, activeAnnotationId, scrollToAnnotation, onCreateAnnotation, onActivateAnnotation, annotationEditorHandleRef, annotationLogger, className }: AppProps): import("react").JSX.Element;
|
|
64
|
+
export {};
|
package/dist/app/App.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
|
3
|
+
import { Editor } from './editor/index.js';
|
|
4
|
+
import { useEditorHost } from '../host/context.js';
|
|
5
|
+
import { useHostMessages } from '../host/messages.js';
|
|
6
|
+
// Simple diff algorithm to find the changed region between two strings
|
|
7
|
+
function computeMinimalEdits(oldText, newText) {
|
|
8
|
+
if (oldText === newText)
|
|
9
|
+
return [];
|
|
10
|
+
// Find common prefix
|
|
11
|
+
let prefixLen = 0;
|
|
12
|
+
const minLen = Math.min(oldText.length, newText.length);
|
|
13
|
+
while (prefixLen < minLen && oldText[prefixLen] === newText[prefixLen]) {
|
|
14
|
+
prefixLen++;
|
|
15
|
+
}
|
|
16
|
+
// Find common suffix (but don't overlap with prefix)
|
|
17
|
+
let suffixLen = 0;
|
|
18
|
+
while (suffixLen < minLen - prefixLen &&
|
|
19
|
+
oldText[oldText.length - 1 - suffixLen] === newText[newText.length - 1 - suffixLen]) {
|
|
20
|
+
suffixLen++;
|
|
21
|
+
}
|
|
22
|
+
// Calculate the changed region
|
|
23
|
+
const start = prefixLen;
|
|
24
|
+
const end = oldText.length - suffixLen;
|
|
25
|
+
const newTextContent = newText.slice(prefixLen, newText.length - suffixLen);
|
|
26
|
+
return [{
|
|
27
|
+
start,
|
|
28
|
+
end,
|
|
29
|
+
newText: newTextContent,
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
export function App({ editable = true, autoFocus = false, content: propContent, onChange: propOnChange, filePath, resolveLocalAsset, wikiLinkPromotion, onSelectionContextMenu, onSubstitutionDetected, sweepRef, annotationKinds, annotations, activeAnnotationId, scrollToAnnotation, onCreateAnnotation, onActivateAnnotation, annotationEditorHandleRef, annotationLogger, className = 'min-h-screen p-0' }) {
|
|
33
|
+
const { bridge, logger } = useEditorHost();
|
|
34
|
+
const log = useMemo(() => logger('slashmd/App'), [logger]);
|
|
35
|
+
const { requestInit, applyTextEdits } = useHostMessages();
|
|
36
|
+
const [internalContent, setInternalContent] = useState(null);
|
|
37
|
+
const [settings, setSettings] = useState(null);
|
|
38
|
+
const [assetBaseUri, setAssetBaseUri] = useState(undefined);
|
|
39
|
+
const [ipcDocumentDirUri, setIpcDocumentDirUri] = useState(undefined);
|
|
40
|
+
// Derive documentDirUri from filePath when not provided via IPC (inline mode).
|
|
41
|
+
// This ensures relative image paths like "image.svg" resolve relative to the document's directory.
|
|
42
|
+
const documentDirUri = useMemo(() => {
|
|
43
|
+
if (ipcDocumentDirUri)
|
|
44
|
+
return ipcDocumentDirUri;
|
|
45
|
+
if (!filePath)
|
|
46
|
+
return undefined;
|
|
47
|
+
const lastSlash = filePath.lastIndexOf('/');
|
|
48
|
+
return lastSlash >= 0 ? filePath.slice(0, lastSlash + 1) : '';
|
|
49
|
+
}, [ipcDocumentDirUri, filePath]);
|
|
50
|
+
const [themeOverrides, setThemeOverrides] = useState(undefined);
|
|
51
|
+
const [error, setError] = useState(null);
|
|
52
|
+
const pendingAssetCallback = useRef(null);
|
|
53
|
+
// Track the last known document content for diff computation
|
|
54
|
+
const lastDocumentContent = useRef('');
|
|
55
|
+
// Cursor state - persists across content reloads
|
|
56
|
+
const cursorStateRef = useRef(null);
|
|
57
|
+
// Counter to trigger cursor restoration after content changes
|
|
58
|
+
const [contentVersion, setContentVersion] = useState(0);
|
|
59
|
+
// Use prop content if provided, otherwise use internal state
|
|
60
|
+
const content = propContent !== undefined ? propContent : internalContent;
|
|
61
|
+
// Track when editor-initiated changes happen to skip cursor restoration
|
|
62
|
+
const lastEditorChangeTime = useRef(0);
|
|
63
|
+
// Track prop content changes to update lastDocumentContent and trigger cursor restoration
|
|
64
|
+
// BUT only increment contentVersion for EXTERNAL changes (file switch, reload)
|
|
65
|
+
// NOT for changes that came from the editor itself (typing)
|
|
66
|
+
const prevPropContent = useRef(propContent);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (propContent !== undefined && propContent !== prevPropContent.current) {
|
|
69
|
+
const timeSinceEditorChange = Date.now() - lastEditorChangeTime.current;
|
|
70
|
+
const isEditorInitiated = timeSinceEditorChange < 500;
|
|
71
|
+
log.info('Content prop changed', {
|
|
72
|
+
chars: propContent.length,
|
|
73
|
+
isEditorInitiated,
|
|
74
|
+
timeSinceEditorChange
|
|
75
|
+
});
|
|
76
|
+
lastDocumentContent.current = propContent;
|
|
77
|
+
// Only increment contentVersion for external changes (triggers cursor restore)
|
|
78
|
+
// Skip for editor-initiated changes (would mess up cursor during typing)
|
|
79
|
+
if (!isEditorInitiated) {
|
|
80
|
+
setContentVersion((v) => v + 1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
prevPropContent.current = propContent;
|
|
84
|
+
}, [propContent, log]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
log.info('Setting up message handler');
|
|
87
|
+
const removeHandler = bridge.addMessageHandler((message) => {
|
|
88
|
+
log.debug('Handling message', message.type);
|
|
89
|
+
switch (message.type) {
|
|
90
|
+
case 'DOC_INIT':
|
|
91
|
+
// Only use IPC content if not controlled by prop
|
|
92
|
+
if (propContent === undefined) {
|
|
93
|
+
log.info('DOC_INIT', { chars: message.text?.length });
|
|
94
|
+
lastDocumentContent.current = message.text;
|
|
95
|
+
setInternalContent(message.text);
|
|
96
|
+
}
|
|
97
|
+
setSettings(message.settings);
|
|
98
|
+
setAssetBaseUri(message.assetBaseUri);
|
|
99
|
+
setIpcDocumentDirUri(message.documentDirUri);
|
|
100
|
+
setThemeOverrides(message.themeOverrides);
|
|
101
|
+
break;
|
|
102
|
+
case 'DOC_CHANGED':
|
|
103
|
+
// Only use IPC content if not controlled by prop
|
|
104
|
+
if (propContent === undefined) {
|
|
105
|
+
log.debug('DOC_CHANGED', { chars: message.text?.length });
|
|
106
|
+
// Skip if content matches what we already have (our own save echoing back)
|
|
107
|
+
if (message.text === lastDocumentContent.current) {
|
|
108
|
+
log.debug('DOC_CHANGED skipped - content unchanged');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
log.info('DOC_CHANGED - content differs, will restore cursor', {
|
|
112
|
+
cursorOffset: cursorStateRef.current?.offset,
|
|
113
|
+
});
|
|
114
|
+
lastDocumentContent.current = message.text;
|
|
115
|
+
setInternalContent(message.text);
|
|
116
|
+
// Increment version to signal Editor to restore cursor
|
|
117
|
+
setContentVersion((v) => v + 1);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'SETTINGS_CHANGED':
|
|
121
|
+
setSettings(message.settings);
|
|
122
|
+
setThemeOverrides(message.themeOverrides);
|
|
123
|
+
break;
|
|
124
|
+
case 'ASSET_WRITTEN':
|
|
125
|
+
if (pendingAssetCallback.current) {
|
|
126
|
+
pendingAssetCallback.current(message.relPath);
|
|
127
|
+
pendingAssetCallback.current = null;
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case 'ERROR':
|
|
131
|
+
log.warn('ERROR', message.message);
|
|
132
|
+
setError(message.message);
|
|
133
|
+
setTimeout(() => setError(null), 5000);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Only request init if not controlled by prop
|
|
138
|
+
if (propContent === undefined) {
|
|
139
|
+
requestInit();
|
|
140
|
+
}
|
|
141
|
+
return removeHandler;
|
|
142
|
+
}, [propContent, bridge, requestInit, log]);
|
|
143
|
+
// Apply theme overrides as CSS variables
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (!themeOverrides)
|
|
146
|
+
return;
|
|
147
|
+
const root = document.documentElement;
|
|
148
|
+
for (const [property, value] of Object.entries(themeOverrides)) {
|
|
149
|
+
root.style.setProperty(property, value);
|
|
150
|
+
}
|
|
151
|
+
}, [themeOverrides]);
|
|
152
|
+
const handleChange = useCallback((markdown) => {
|
|
153
|
+
// Calculate diff and send minimal edits
|
|
154
|
+
const edits = computeMinimalEdits(lastDocumentContent.current, markdown);
|
|
155
|
+
if (edits.length > 0) {
|
|
156
|
+
// This can happen even without typing if the editor normalizes/serializes markdown
|
|
157
|
+
// (e.g., whitespace normalization). Logging helps verify what's being changed.
|
|
158
|
+
log.debug('onChange -> applyTextEdits', {
|
|
159
|
+
edits: edits.map((e) => ({ start: e.start, end: e.end, newTextLen: e.newText.length })),
|
|
160
|
+
beforeLen: lastDocumentContent.current.length,
|
|
161
|
+
afterLen: markdown.length,
|
|
162
|
+
});
|
|
163
|
+
lastDocumentContent.current = markdown;
|
|
164
|
+
applyTextEdits(edits, 'typing');
|
|
165
|
+
// Mark this as an editor-initiated change BEFORE calling propOnChange
|
|
166
|
+
// This prevents the prop change from triggering cursor restoration
|
|
167
|
+
lastEditorChangeTime.current = Date.now();
|
|
168
|
+
// Call prop onChange if provided
|
|
169
|
+
propOnChange?.(markdown);
|
|
170
|
+
}
|
|
171
|
+
}, [propOnChange, applyTextEdits, log]);
|
|
172
|
+
// Track cursor position whenever it changes
|
|
173
|
+
const handleCursorChange = useCallback((cursor) => {
|
|
174
|
+
cursorStateRef.current = cursor;
|
|
175
|
+
}, []);
|
|
176
|
+
if (content === null) {
|
|
177
|
+
return (_jsxs("div", { className: "flex items-center justify-center gap-3 min-h-screen text-[var(--vscode-foreground)] opacity-70", children: [_jsx("div", { className: "loading-spinner" }), _jsx("span", { children: "Loading document..." })] }));
|
|
178
|
+
}
|
|
179
|
+
return (_jsxs("div", { className: className, children: [error && (_jsx("div", { className: "fixed top-0 left-0 right-0 px-4 py-2 bg-red-500 text-white text-center z-[1000]", role: "alert", children: error })), _jsx(Editor, { initialContent: content, autoFocus: autoFocus, contentVersion: contentVersion, cursorToRestoreRef: cursorStateRef, onChange: handleChange, onCursorChange: handleCursorChange, assetBaseUri: assetBaseUri, documentDirUri: documentDirUri, imagePathResolution: settings?.imagePathResolution ?? 'document', wikiLinkPromotion: wikiLinkPromotion, resolveLocalAsset: resolveLocalAsset, editable: editable, filePath: filePath, onSelectionContextMenu: onSelectionContextMenu, onSubstitutionDetected: onSubstitutionDetected, sweepRef: sweepRef, annotationKinds: annotationKinds, annotations: annotations, activeAnnotationId: activeAnnotationId, scrollToAnnotation: scrollToAnnotation, onCreateAnnotation: onCreateAnnotation, onActivateAnnotation: onActivateAnnotation, annotationEditorHandleRef: annotationEditorHandleRef, annotationLogger: annotationLogger })] }));
|
|
180
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type SweepFn = (oldTerm: string, newTerm: string) => Promise<number>;
|
|
2
|
+
interface AmbientCorrectionPluginProps {
|
|
3
|
+
onSubstitutionDetected: (oldTerm: string, newTerm: string) => void;
|
|
4
|
+
sweepRef?: React.MutableRefObject<SweepFn | null>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Ambient correction plugin.
|
|
8
|
+
*
|
|
9
|
+
* 1. Detects single-word substitutions (debounced 300 ms) and calls
|
|
10
|
+
* `onSubstitutionDetected` so the parent can show a nudge toast.
|
|
11
|
+
*
|
|
12
|
+
* 2. Populates `sweepRef.current` with a function that performs the confirmed
|
|
13
|
+
* sweep: replaces all remaining occurrences of `oldTerm` with `newTerm`
|
|
14
|
+
* (case-preserving, skipping code blocks and frontmatter) inside a single
|
|
15
|
+
* `editor.update()` call so undo produces one history entry.
|
|
16
|
+
*/
|
|
17
|
+
export declare function AmbientCorrectionPlugin({ onSubstitutionDetected, sweepRef, }: AmbientCorrectionPluginProps): null;
|
|
18
|
+
export {};
|