@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
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brett Adam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This package vendors a modified copy of `mdast-util-wiki-link` (MIT,
|
|
26
|
+
Copyright (c) 2020 Mark Hudnall). Its license and a description of the
|
|
27
|
+
modifications ship alongside it, at
|
|
28
|
+
`dist/markdown/vendor/mdast-util-wiki-link/`.
|
|
29
|
+
|
|
30
|
+
This package derives from the `webview-ui` package of SlashMD
|
|
31
|
+
(https://github.com/wolfdavo/SlashMD) by David Wolfenden, which that project's
|
|
32
|
+
README declares to be MIT-licensed. SlashMD carries no LICENSE file and no
|
|
33
|
+
copyright notice, so none is reproduced here; this notice records the
|
|
34
|
+
derivation and the stated license in their absence.
|
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# `@liminis/editor`
|
|
2
|
+
|
|
3
|
+
A Lexical-based markdown WYSIWYG editor that round-trips through mdast, with a
|
|
4
|
+
host-injection seam so it can be embedded in an Electron app, a browser app, or
|
|
5
|
+
anything else that can supply a message channel.
|
|
6
|
+
|
|
7
|
+
It is the editor from [Liminis](https://github.com/verveguy/liminis), extracted
|
|
8
|
+
into this repository so it can stand on its own. MIT-licensed.
|
|
9
|
+
|
|
10
|
+
Its history, and how to read the `ADR-0NNN`, `FR-NNN` and `#NNN` references that
|
|
11
|
+
travelled with the code, are recorded in [`docs/provenance.md`](./docs/provenance.md).
|
|
12
|
+
|
|
13
|
+
## What you get
|
|
14
|
+
|
|
15
|
+
- **`<Editor>`** — a WYSIWYG markdown editor. Tables, task lists (including in
|
|
16
|
+
ordered lists), footnotes, definition lists, callouts, toggles, code blocks
|
|
17
|
+
with Prism highlighting, images, LaTeX equations, Mermaid diagrams, C4
|
|
18
|
+
diagrams, YAML frontmatter, and wiki-links.
|
|
19
|
+
- **A markdown pipeline** — `parseMarkdown` / `stringifyMarkdown` and the mdast
|
|
20
|
+
↔ Lexical mappers, usable with no editor mounted.
|
|
21
|
+
- **An annotation mechanism** — range-anchored markers over document text that
|
|
22
|
+
survive edits, with per-*kind* configuration. Comments and corrections are not
|
|
23
|
+
separate features; they are two configurations of one mechanism. Entirely
|
|
24
|
+
opt-in: configure no kinds and none of it loads.
|
|
25
|
+
- **A host seam** — every service the editor needs from its environment is an
|
|
26
|
+
optional injected function with a safe default, so `<Editor>` renders in a
|
|
27
|
+
host that supplies nothing at all.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
> **This package is not published.** It is `private: true` and `@liminis/editor`
|
|
32
|
+
> does not exist on the npm registry, so `pnpm add @liminis/editor` will fail.
|
|
33
|
+
> That is deliberate — see [Consuming it](#consuming-it) below for the mechanism
|
|
34
|
+
> that actually works, and `docs/decisions/adr-078.md` for why.
|
|
35
|
+
|
|
36
|
+
React and Lexical are **peer dependencies**, so your app resolves exactly one
|
|
37
|
+
copy of each. Two React copies produce `Invalid hook call`; two Lexical copies
|
|
38
|
+
produce a broken editor context — peering is what prevents both.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add react@^19 react-dom@^19 \
|
|
42
|
+
lexical@^0.44 @lexical/react@^0.44 @lexical/code@^0.44 \
|
|
43
|
+
@lexical/code-prism@^0.44 @lexical/link@^0.44 @lexical/list@^0.44 \
|
|
44
|
+
@lexical/mark@^0.44 @lexical/markdown@^0.44 @lexical/rich-text@^0.44 \
|
|
45
|
+
@lexical/selection@^0.44 @lexical/table@^0.44 @lexical/utils@^0.44
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
pnpm v8+ and npm v7+ install peers automatically, so in practice the first
|
|
49
|
+
command is usually enough.
|
|
50
|
+
|
|
51
|
+
The package ships ESM plus `.d.ts` declarations. It has no CommonJS build.
|
|
52
|
+
|
|
53
|
+
## Quickstart
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { useState } from 'react'
|
|
57
|
+
import { Editor } from '@liminis/editor'
|
|
58
|
+
import '@liminis/editor/styles.css' // required — see "Styling" below
|
|
59
|
+
|
|
60
|
+
export function MyEditor() {
|
|
61
|
+
const [markdown, setMarkdown] = useState('# Hello\n\nStart typing.')
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Editor
|
|
65
|
+
initialContent={markdown}
|
|
66
|
+
onChange={setMarkdown}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`initialContent` is markdown text; `onChange` receives markdown text back.
|
|
73
|
+
The editor owns the Lexical state in between — you never see it unless you want
|
|
74
|
+
to.
|
|
75
|
+
|
|
76
|
+
## Entry points
|
|
77
|
+
|
|
78
|
+
Seven, and they are not stylistic. Each keeps a specific dependency graph out of
|
|
79
|
+
a specific consumer, and importing the wrong one is measured in megabytes.
|
|
80
|
+
|
|
81
|
+
| Entry | Contains | Import it when | Lexical-free |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `@liminis/editor` | Everything: `<Editor>`, `<App>`, the host seam, the markdown pipeline, the mappers | You are rendering an editor | No |
|
|
84
|
+
| `@liminis/editor/markdown` | `parseMarkdown`, mdast type guards, `getFileType`, the wiki-link mdast extension. Pure mdast/micromark | You only need to *read* markdown — a search snippet, a table of contents, a chunker | Yes |
|
|
85
|
+
| `@liminis/editor/annotations` | The anchor model, resolver, block structure, and annotation types. DOM-, React- and Lexical-free | You are resolving or storing annotations outside a rendered editor | Yes |
|
|
86
|
+
| `@liminis/editor/headless` | The C4 subsystem, server-side SVG rendering, MathJax lite adaptor | You are rendering diagrams or equations with no DOM (a server, a worker, an Electron main process) | Yes |
|
|
87
|
+
| `@liminis/editor/contract` | The host-message shapes, as types | You are writing a boundary that must not pull renderer code in — an Electron preload script. Use `import type` | Yes (type-only) |
|
|
88
|
+
| `@liminis/editor/nodes` | `editorNodes` (the exact Lexical node array `<Editor>` configures itself with), plus `importMarkdownToLexical` / `exportLexicalToMdast` | You are building your own headless Lexical `createEditor` — for example, to test the markdown↔Lexical mapper without mounting `<Editor>` | No |
|
|
89
|
+
| `@liminis/editor/styles.css` | The stylesheet | Always, once, in your app | N/A (not JS) |
|
|
90
|
+
|
|
91
|
+
Three of these are load-bearing in ways that are easy to undo by accident:
|
|
92
|
+
|
|
93
|
+
- **`./markdown` is deliberately *not* `./headless`.** `./headless` re-exports
|
|
94
|
+
the MathJax configuration, whose ~90 bare
|
|
95
|
+
`import '@mathjax/src/js/input/tex/…Configuration.js'` lines are genuinely
|
|
96
|
+
side-effectful and cannot be tree-shaken by anyone — roughly 1.9 MB. If all
|
|
97
|
+
you do is parse markdown, import `./markdown` and you never pay it.
|
|
98
|
+
- **`./contract` is only type-free of `zod` if you `import type`.** A value
|
|
99
|
+
import pulls the schemas in.
|
|
100
|
+
- **`./nodes` is the one entry that legitimately requires Lexical and React** —
|
|
101
|
+
its whole purpose is exporting the node classes the mapper instantiates. It
|
|
102
|
+
inherits `./headless`'s MathJax-lite exception (loading it evaluates the same
|
|
103
|
+
~90 side-effectful TeX-configuration imports, via `EquationNode`), but it
|
|
104
|
+
pulls in none of Mermaid's, C4's, or Prism's rendering weight — those
|
|
105
|
+
decorator components are lazy-loaded and never evaluate in a headless editor.
|
|
106
|
+
|
|
107
|
+
Do not deep-import into `dist/`. Everything intended for consumers is on one of
|
|
108
|
+
the seven entries above; anything else is internal and will move.
|
|
109
|
+
|
|
110
|
+
### Why there's no `sideEffects` field
|
|
111
|
+
|
|
112
|
+
`package.json` deliberately does not declare `sideEffects`. If your bundler's
|
|
113
|
+
tree-shaking pass ever runs against this package's own graph and you're
|
|
114
|
+
tempted to add one to prune it further, don't — the field is permanently
|
|
115
|
+
prohibited (ADR-075 §4), not merely undeclared for now.
|
|
116
|
+
|
|
117
|
+
The failure it would cause is a transitive one: marking this package's modules
|
|
118
|
+
side-effect-free changes how a whole-graph bundler like rolldown chunks
|
|
119
|
+
everything reachable from it, including third-party code the package merely
|
|
120
|
+
imports. `prismjs`, pulled in via `@lexical/code-prism`, has its core chunk
|
|
121
|
+
separated from `prism-clike.js`, a companion module that mutates a bare
|
|
122
|
+
`Prism` global at import time. Once the two are split into different chunks,
|
|
123
|
+
`prism-clike.js` runs before `Prism` exists:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
ReferenceError: Prism is not defined
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The app that hits this doesn't fail to build — it fails to *render*, since the
|
|
130
|
+
error is thrown by code Prism's own language-grammar files depend on running
|
|
131
|
+
first. This can't be fixed from inside this package: the hazard lives in
|
|
132
|
+
`prismjs`'s own module structure, three levels removed, so no `sideEffects`
|
|
133
|
+
value this package could declare about *its own* files changes how the bundler
|
|
134
|
+
chunks `prismjs`.
|
|
135
|
+
|
|
136
|
+
## Versioning policy
|
|
137
|
+
|
|
138
|
+
This package is `0.x`. While it stays there:
|
|
139
|
+
|
|
140
|
+
- **Minor versions may break.** `0.1.0` → `0.2.0` is not guaranteed compatible.
|
|
141
|
+
- **Patch versions may not.** `0.1.0` → `0.1.1` is a bug fix only.
|
|
142
|
+
- **The seven subpaths above are the supported API.** Anything reachable only
|
|
143
|
+
by a deeper import path — `@liminis/editor/dist/...` or a relative path into
|
|
144
|
+
`src/` — is private. It can move or disappear in a patch release.
|
|
145
|
+
|
|
146
|
+
## Styling
|
|
147
|
+
|
|
148
|
+
The editor's markup carries semantic class names (`editor-paragraph`,
|
|
149
|
+
`editor-heading-h1`, `annotation-mark-*`, …). **`@liminis/editor/styles.css`
|
|
150
|
+
defines them.** Importing it is not optional — without it the editor renders as
|
|
151
|
+
unstyled text, and annotation markers are invisible. This failure is silent:
|
|
152
|
+
nothing errors, it just looks wrong.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import '@liminis/editor/styles.css'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The sheet is plain CSS — OKLCH design tokens plus the editor's own rules. It has
|
|
159
|
+
no build-step requirement and works with any bundler.
|
|
160
|
+
|
|
161
|
+
### TypeScript needs to be told CSS imports exist
|
|
162
|
+
|
|
163
|
+
If you type-check your app, that import is an error out of the box:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
error TS2882: Cannot find module or type declarations for side-effect import
|
|
167
|
+
of '@liminis/editor/styles.css'.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This is a TypeScript language limitation, not something the package can fix from
|
|
171
|
+
its side: TypeScript has no built-in meaning for a `.css` module, so *any*
|
|
172
|
+
stylesheet import from *any* package needs an ambient declaration. Most React
|
|
173
|
+
starters already provide one and you will never see this. If yours does not, one
|
|
174
|
+
line anywhere in your project's `include` fixes it:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// css.d.ts
|
|
178
|
+
declare module '*.css'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
If you are on Vite, adding its client types does the same job and covers assets
|
|
182
|
+
too:
|
|
183
|
+
|
|
184
|
+
```jsonc
|
|
185
|
+
// tsconfig.json
|
|
186
|
+
{ "compilerOptions": { "types": ["vite/client"] } }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Note the contrast with the failure above: forgetting the *import* fails
|
|
190
|
+
silently, while adding it without a declaration fails loudly at compile time.
|
|
191
|
+
Neither is a package defect, but only one of them tells you what is wrong.
|
|
192
|
+
|
|
193
|
+
### If you use Tailwind
|
|
194
|
+
|
|
195
|
+
Some of the editor's markup also uses Tailwind utility classes. Tailwind v4
|
|
196
|
+
generates utilities by scanning your source, and **its automatic source
|
|
197
|
+
detection skips `node_modules`** — so without telling it where to look, those
|
|
198
|
+
classes are simply absent. Again: silent, not an error.
|
|
199
|
+
|
|
200
|
+
Add an explicit `@source` for the package's built output in the CSS file where
|
|
201
|
+
you import Tailwind:
|
|
202
|
+
|
|
203
|
+
```css
|
|
204
|
+
@import "tailwindcss";
|
|
205
|
+
@source "../node_modules/@liminis/editor/dist";
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Adjust the relative path to point at your installed copy. In a pnpm workspace
|
|
209
|
+
the real path may be under `node_modules/.pnpm/` — pointing `@source` at the
|
|
210
|
+
symlinked path works, but verify a distinctive utility class actually appears in
|
|
211
|
+
your generated CSS rather than assuming it.
|
|
212
|
+
|
|
213
|
+
## Annotations
|
|
214
|
+
|
|
215
|
+
An annotation is a range-anchored marker over document text whose anchor
|
|
216
|
+
survives edits to the surrounding document. The mechanism is entirely opt-in:
|
|
217
|
+
supplying an `annotationKinds` prop is what turns it on. With no kinds
|
|
218
|
+
configured, the annotation UI is never loaded — it sits behind a `React.lazy()`
|
|
219
|
+
boundary, so it is not even in the chunk your app downloads.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<Editor
|
|
223
|
+
initialContent={markdown}
|
|
224
|
+
onChange={setMarkdown}
|
|
225
|
+
annotationKinds={{
|
|
226
|
+
comment: {
|
|
227
|
+
markerStyle: 'highlight',
|
|
228
|
+
createAffordance: { surface: 'toolbar', label: 'Comment' },
|
|
229
|
+
},
|
|
230
|
+
}}
|
|
231
|
+
annotations={annotations}
|
|
232
|
+
onCreateAnnotation={(event) => { /* mint an id, persist, add to `annotations` */ }}
|
|
233
|
+
onActivateAnnotation={(id) => { /* open your own thread panel */ }}
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The package owns anchor mechanics and marker rendering. Identity, storage and
|
|
238
|
+
lifecycle stay in your app — `id` is opaque to the package and `payload` is
|
|
239
|
+
carried through untouched. See [`docs/annotations.md`](./docs/annotations.md).
|
|
240
|
+
|
|
241
|
+
Configuring more than one kind on the same `createAffordance.surface` offers
|
|
242
|
+
one toolbar button or context-menu entry per kind, in `annotationKinds`'
|
|
243
|
+
declaration order.
|
|
244
|
+
|
|
245
|
+
A `toolbar`-surfaced affordance works the same way whether `editable` is
|
|
246
|
+
`true` or `false` — annotating is decoupled from editing, so a selection in a
|
|
247
|
+
read-only editor still shows the floating toolbar with the configured
|
|
248
|
+
affordance (formatting controls are omitted there, since they would be
|
|
249
|
+
inert).
|
|
250
|
+
|
|
251
|
+
## Documentation
|
|
252
|
+
|
|
253
|
+
- [`docs/editor-api.md`](./docs/editor-api.md) — the `<Editor>` props and the
|
|
254
|
+
host-injection seam.
|
|
255
|
+
- [`docs/markdown-pipeline.md`](./docs/markdown-pipeline.md) — `parseMarkdown`,
|
|
256
|
+
`stringifyMarkdown`, the mdast ↔ Lexical mappers, and wiki-links.
|
|
257
|
+
- [`docs/annotations.md`](./docs/annotations.md) — kinds, anchors, resolution,
|
|
258
|
+
and marker styling.
|
|
259
|
+
|
|
260
|
+
## Consuming it
|
|
261
|
+
|
|
262
|
+
Because the package is unpublished, consumers install a **packed tarball** rather
|
|
263
|
+
than a registry version:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# in this repository
|
|
267
|
+
pnpm build && pnpm pack --pack-destination /tmp
|
|
268
|
+
|
|
269
|
+
# in the consuming application
|
|
270
|
+
pnpm add /tmp/liminis-editor-0.1.0.tgz
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Applications that do this commit the tarball into their own tree (typically under
|
|
274
|
+
`vendor/`) and pin it by filename, so the install is reproducible and needs no
|
|
275
|
+
credentials for this private repository. `pnpm pack` works on a private package;
|
|
276
|
+
only `npm publish` is blocked, which is exactly the guard intended.
|
|
277
|
+
|
|
278
|
+
A git dependency on this repository does **not** work, and the reasons are
|
|
279
|
+
measured rather than assumed: `files` excludes the directory `main` points at, so
|
|
280
|
+
a git install lands only the manifest and docs; the build runs from `prepack`,
|
|
281
|
+
which a git install never invokes; and pnpm does not apply `publishConfig` for git
|
|
282
|
+
installs. Adding a `prepare` script to work around this would change what an
|
|
283
|
+
eventual npm tarball ships, so it has been deliberately avoided.
|
|
284
|
+
|
|
285
|
+
## Demo
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pnpm demo
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Builds the package, packs it, installs the tarball into `examples/demo/` outside
|
|
292
|
+
any workspace, and starts the Vite dev server. The demo imports only the public
|
|
293
|
+
entry points, so it breaks when an adopter would break. Its content is a picker
|
|
294
|
+
over the same round-trip fixture corpus the package's own test suite uses, so
|
|
295
|
+
every fixture-representable node class renders somewhere in it. See
|
|
296
|
+
[`examples/demo/README.md`](./examples/demo/README.md).
|
|
297
|
+
|
|
298
|
+
`examples/demo` is also the source of the public GitHub Pages site: a
|
|
299
|
+
`release`-triggered (not merge-triggered) build of this same shell, showing a
|
|
300
|
+
visible version badge for the published release it represents and a
|
|
301
|
+
Documentation tab rendering this README. Between releases the deployed site
|
|
302
|
+
stays on the last published version even as `main` keeps moving — that
|
|
303
|
+
staleness is intentional, not a bug (see `docs/decisions/adr-082.md`).
|
|
304
|
+
|
|
305
|
+
## Electron e2e shell
|
|
306
|
+
|
|
307
|
+
`examples/electron/` is a minimal Electron host for the package — a window, the
|
|
308
|
+
editor, and stubbed host services, no more — that a Playwright e2e suite drives
|
|
309
|
+
over CDP against real Chromium/Electron rather than a jsdom-class test
|
|
310
|
+
environment. It exists so package-level e2e can catch defects that only ever
|
|
311
|
+
surface in a real browser without needing `liminis-app`, or any other downstream
|
|
312
|
+
application, to exist. See
|
|
313
|
+
[`examples/electron/README.md`](./examples/electron/README.md).
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
pnpm build:examples # builds and packs the package once, then builds both
|
|
317
|
+
# examples/demo and examples/electron against it
|
|
318
|
+
pnpm build:site # builds examples/demo only — what the release-triggered
|
|
319
|
+
# Pages deploy runs (see the Demo section above)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Development
|
|
323
|
+
|
|
324
|
+
This repository stands alone. It has no `pnpm-workspace.yaml` and no dependency
|
|
325
|
+
on any other checkout — the `examples/` directories are external consumers by
|
|
326
|
+
construction, not workspace members.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
pnpm install
|
|
330
|
+
pnpm build # tsc emit + tsc-alias + asset copy → dist/
|
|
331
|
+
pnpm typecheck # tsc --noEmit
|
|
332
|
+
pnpm lint # eslint src/ tests/
|
|
333
|
+
pnpm test # vitest run
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
That sequence is what CI runs, in that order. One further gate needs a real built
|
|
337
|
+
artifact and so runs separately:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
pnpm verify:package
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
This builds, packs, installs the tarball into `examples/external-consumer/` with
|
|
344
|
+
`--ignore-workspace`, type-checks it under `bundler` and `nodenext` module
|
|
345
|
+
resolution, builds three measurement arms, and asserts the entry-graph boundaries
|
|
346
|
+
the seven export subpaths exist to keep — that `./markdown` pulls in no MathJax,
|
|
347
|
+
no Lexical and no react-dom, and that annotations stay behind a lazy boundary.
|
|
348
|
+
It is the check that proves the package is publishable without publishing it.
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT. See [LICENSE](./LICENSE).
|
|
353
|
+
|
|
354
|
+
This package vendors a modified copy of `mdast-util-wiki-link` (MIT, Mark
|
|
355
|
+
Hudnall); its license and the modifications are documented at
|
|
356
|
+
`dist/markdown/vendor/mdast-util-wiki-link/`.
|
|
357
|
+
|
|
358
|
+
## Acknowledgements
|
|
359
|
+
|
|
360
|
+
This editor began as the `webview-ui` package of
|
|
361
|
+
[**SlashMD**](https://github.com/wolfdavo/SlashMD) by David Wolfenden (MIT, as
|
|
362
|
+
declared in that project's README) — a VS Code extension that opens Markdown
|
|
363
|
+
files in a block-based WYSIWYG editor while keeping the file itself plain
|
|
364
|
+
Markdown.
|
|
365
|
+
|
|
366
|
+
That is the origin of the substance of this package, not a passing influence:
|
|
367
|
+
the Lexical node set (callouts, toggles, equations, Mermaid, frontmatter,
|
|
368
|
+
images), the mdast ↔ Lexical mappers, the Markdown parsing and stringification
|
|
369
|
+
layer, and the host-message contract all descend from it. The contract's shape
|
|
370
|
+
still shows its beginnings — `REQUEST_INIT`, `REQUEST_SETTINGS`, `WRITE_ASSET`
|
|
371
|
+
were a VS Code webview protocol before they were carried over an Electron IPC
|
|
372
|
+
bridge — and identifiers such as `SlashMDSettings` keep the name outright.
|
|
373
|
+
|
|
374
|
+
What has changed since is the seam rather than the ideas: a curated set of
|
|
375
|
+
export subpaths, peer-dependency inversion, an annotation mechanism, and a
|
|
376
|
+
round-trip corpus that holds the Markdown pipeline to a fixed point.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
|
|
3
|
+
*
|
|
4
|
+
* The doc comments below are the original author's and are kept verbatim so
|
|
5
|
+
* this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
|
|
6
|
+
* identifiers, `#NN` issue references and `adrs/` paths therefore name
|
|
7
|
+
* **Zusammen's** spec, issues and ADRs — not this repository's, where the same
|
|
8
|
+
* identifiers mean something else entirely. For the Liminis-side design see
|
|
9
|
+
* `docs/decisions/adr-077.md` and
|
|
10
|
+
* `docs/zusammen-editor-capability-map.md`.
|
|
11
|
+
*
|
|
12
|
+
* "Comment"/"thread" in these comments should be read as "annotation": this
|
|
13
|
+
* module now serves both annotation kinds, not comments alone.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Comment Anchor Alignment
|
|
17
|
+
*
|
|
18
|
+
* `locateInSpan`: the verbatim-substring, context/occurrence-disambiguated
|
|
19
|
+
* text search shared by both directions of the comment-anchor mapping
|
|
20
|
+
* (capture and marker placement) — reused unchanged by #43's live-mark
|
|
21
|
+
* mechanism (`editor/app/editor/comment-anchor-marks.ts`) for the same
|
|
22
|
+
* lookup it always did: `targetText` -> location within the current raw
|
|
23
|
+
* markdown. Pure, git-free — no fs, no git, no DOM — safe to unit test with
|
|
24
|
+
* plain data.
|
|
25
|
+
*/
|
|
26
|
+
export interface SpanRange {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
}
|
|
30
|
+
export interface LocateInSpanOptions {
|
|
31
|
+
/** 0-based index of which occurrence of `target` to prefer, when known. */
|
|
32
|
+
occurrenceIndex?: number;
|
|
33
|
+
/** Text immediately before the target, for disambiguating duplicate occurrences. */
|
|
34
|
+
prefixContext?: string;
|
|
35
|
+
/** Text immediately after the target, for disambiguating duplicate occurrences. */
|
|
36
|
+
suffixContext?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Locate `target` verbatim within `spanText`. Returns null if `target` does
|
|
40
|
+
* not appear at all — the caller falls back to anchoring the whole enclosing
|
|
41
|
+
* block (the Plan's bounded v1 scope for selections that straddle inline
|
|
42
|
+
* markdown syntax, e.g. a selection starting mid-`**bold**`).
|
|
43
|
+
*
|
|
44
|
+
* When `target` appears more than once, candidates are ranked by how well
|
|
45
|
+
* their surrounding context matches `prefixContext`/`suffixContext` plus
|
|
46
|
+
* proximity to `occurrenceIndex` — the same disambiguation signal
|
|
47
|
+
* `anchor-resolver.ts` uses for duplicate exact matches, reused here (via the
|
|
48
|
+
* shared `similarity` export) rather than reinvented.
|
|
49
|
+
*/
|
|
50
|
+
export declare function locateInSpan(spanText: string, target: string, options?: LocateInSpanOptions): SpanRange | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
|
|
3
|
+
*
|
|
4
|
+
* The doc comments below are the original author's and are kept verbatim so
|
|
5
|
+
* this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
|
|
6
|
+
* identifiers, `#NN` issue references and `adrs/` paths therefore name
|
|
7
|
+
* **Zusammen's** spec, issues and ADRs — not this repository's, where the same
|
|
8
|
+
* identifiers mean something else entirely. For the Liminis-side design see
|
|
9
|
+
* `docs/decisions/adr-077.md` and
|
|
10
|
+
* `docs/zusammen-editor-capability-map.md`.
|
|
11
|
+
*
|
|
12
|
+
* "Comment"/"thread" in these comments should be read as "annotation": this
|
|
13
|
+
* module now serves both annotation kinds, not comments alone.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Comment Anchor Alignment
|
|
17
|
+
*
|
|
18
|
+
* `locateInSpan`: the verbatim-substring, context/occurrence-disambiguated
|
|
19
|
+
* text search shared by both directions of the comment-anchor mapping
|
|
20
|
+
* (capture and marker placement) — reused unchanged by #43's live-mark
|
|
21
|
+
* mechanism (`editor/app/editor/comment-anchor-marks.ts`) for the same
|
|
22
|
+
* lookup it always did: `targetText` -> location within the current raw
|
|
23
|
+
* markdown. Pure, git-free — no fs, no git, no DOM — safe to unit test with
|
|
24
|
+
* plain data.
|
|
25
|
+
*/
|
|
26
|
+
import { similarity } from './anchor-resolver.js';
|
|
27
|
+
/** How many characters of context around a candidate occurrence to compare against `prefixContext`/`suffixContext`. */
|
|
28
|
+
const CONTEXT_COMPARISON_CHARS = 40;
|
|
29
|
+
function findOccurrences(spanText, target) {
|
|
30
|
+
if (!target)
|
|
31
|
+
return [];
|
|
32
|
+
const matches = [];
|
|
33
|
+
let idx = spanText.indexOf(target);
|
|
34
|
+
while (idx !== -1) {
|
|
35
|
+
matches.push({ start: idx, end: idx + target.length });
|
|
36
|
+
idx = spanText.indexOf(target, idx + 1);
|
|
37
|
+
}
|
|
38
|
+
return matches;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Locate `target` verbatim within `spanText`. Returns null if `target` does
|
|
42
|
+
* not appear at all — the caller falls back to anchoring the whole enclosing
|
|
43
|
+
* block (the Plan's bounded v1 scope for selections that straddle inline
|
|
44
|
+
* markdown syntax, e.g. a selection starting mid-`**bold**`).
|
|
45
|
+
*
|
|
46
|
+
* When `target` appears more than once, candidates are ranked by how well
|
|
47
|
+
* their surrounding context matches `prefixContext`/`suffixContext` plus
|
|
48
|
+
* proximity to `occurrenceIndex` — the same disambiguation signal
|
|
49
|
+
* `anchor-resolver.ts` uses for duplicate exact matches, reused here (via the
|
|
50
|
+
* shared `similarity` export) rather than reinvented.
|
|
51
|
+
*/
|
|
52
|
+
export function locateInSpan(spanText, target, options = {}) {
|
|
53
|
+
const matches = findOccurrences(spanText, target);
|
|
54
|
+
if (matches.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
if (matches.length === 1)
|
|
57
|
+
return matches[0];
|
|
58
|
+
const { occurrenceIndex, prefixContext = '', suffixContext = '' } = options;
|
|
59
|
+
// Only weigh in context when the caller actually supplied any — comparing
|
|
60
|
+
// against an empty context would otherwise favor whichever occurrence
|
|
61
|
+
// happens to have the shortest surrounding text, not the intended one.
|
|
62
|
+
const hasContext = prefixContext.length > 0 || suffixContext.length > 0;
|
|
63
|
+
const contextWeight = hasContext ? 0.8 : 0;
|
|
64
|
+
const proximityWeight = 1 - contextWeight;
|
|
65
|
+
const scored = matches.map((match, index) => {
|
|
66
|
+
const prefix = spanText.slice(Math.max(0, match.start - CONTEXT_COMPARISON_CHARS), match.start);
|
|
67
|
+
const suffix = spanText.slice(match.end, Math.min(spanText.length, match.end + CONTEXT_COMPARISON_CHARS));
|
|
68
|
+
const contextScore = (similarity(prefix, prefixContext) + similarity(suffix, suffixContext)) / 2;
|
|
69
|
+
const proximityScore = occurrenceIndex == null ? 0 : 1 - Math.abs(index - occurrenceIndex) / matches.length;
|
|
70
|
+
return { match, score: contextScore * contextWeight + proximityScore * proximityWeight };
|
|
71
|
+
});
|
|
72
|
+
scored.sort((a, b) => b.score - a.score);
|
|
73
|
+
return scored[0].match;
|
|
74
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROVENANCE — ported from Zusammen (`verveguy/zusammen`) for Liminis #939.
|
|
3
|
+
*
|
|
4
|
+
* The doc comments below are the original author's and are kept verbatim so
|
|
5
|
+
* this module stays diffable against its source. Their `FR-NNN`/`SC-NNN`
|
|
6
|
+
* identifiers, `#NN` issue references and `adrs/` paths therefore name
|
|
7
|
+
* **Zusammen's** spec, issues and ADRs — not this repository's, where the same
|
|
8
|
+
* identifiers mean something else entirely. For the Liminis-side design see
|
|
9
|
+
* `docs/decisions/adr-077.md` and
|
|
10
|
+
* `docs/zusammen-editor-capability-map.md`.
|
|
11
|
+
*
|
|
12
|
+
* "Comment"/"thread" in these comments should be read as "annotation": this
|
|
13
|
+
* module now serves both annotation kinds, not comments alone.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Comment Anchor Model
|
|
17
|
+
*
|
|
18
|
+
* Defines the content-relative anchor payload persisted through the
|
|
19
|
+
* comment-event-store's (issue #13) `re-anchor`/`anchor-recorded` events, and
|
|
20
|
+
* the resolution-outcome payload persisted through `anchor-resolved` events.
|
|
21
|
+
* Both were left opaque (`unknown`) by that store on purpose — this module is
|
|
22
|
+
* what gives them shape (FR-001, FR-002).
|
|
23
|
+
*
|
|
24
|
+
* Pure, git-free — no fs, no git, safe to unit test with plain strings.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { type BlockType } from './block-structure.js';
|
|
28
|
+
/** How many characters of surrounding text are captured on each side of the target, for duplicate disambiguation (FR-006). */
|
|
29
|
+
export declare const CONTEXT_WINDOW_CHARS = 40;
|
|
30
|
+
/**
|
|
31
|
+
* The durable, content-relative description of a comment's target (FR-001).
|
|
32
|
+
* Re-locating a target needs only an anchor plus the new document text — never
|
|
33
|
+
* the old document text — which is what makes resolution provenance-agnostic
|
|
34
|
+
* (FR-010): a direct edit and a merge produce the same new text.
|
|
35
|
+
*/
|
|
36
|
+
export interface Anchor {
|
|
37
|
+
/** The exact substring the comment targets. */
|
|
38
|
+
targetText: string;
|
|
39
|
+
/** Up to CONTEXT_WINDOW_CHARS of text immediately before the target. */
|
|
40
|
+
prefixContext: string;
|
|
41
|
+
/** Up to CONTEXT_WINDOW_CHARS of text immediately after the target. */
|
|
42
|
+
suffixContext: string;
|
|
43
|
+
/** The type of the single leaf block enclosing the target at capture time, or null if the target itself spans more than one block. */
|
|
44
|
+
blockType: BlockType | null;
|
|
45
|
+
/** 0-based index of which occurrence of `targetText` in the document this anchor refers to — disambiguates duplicate text (FR-006). */
|
|
46
|
+
occurrenceIndex: number;
|
|
47
|
+
/** The content-repo HEAD SHA the anchor was captured against. */
|
|
48
|
+
docVersion: string;
|
|
49
|
+
}
|
|
50
|
+
/** An anchor as read from the live editor structure, before the store stamps `docVersion`. */
|
|
51
|
+
export type AnchorFields = Omit<Anchor, 'docVersion'>;
|
|
52
|
+
export declare const ANCHOR_SCHEMA: z.ZodType<Anchor>;
|
|
53
|
+
export interface AnchorRange {
|
|
54
|
+
start: number;
|
|
55
|
+
end: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Capture a durable anchor for the text at `range` within `text`, as of
|
|
59
|
+
* `docVersion`. The target's enclosing block type is null when the range
|
|
60
|
+
* spans more than one leaf block (e.g. a comment deliberately covering two
|
|
61
|
+
* paragraphs) — the resolver treats that the same as any other
|
|
62
|
+
* structural-boundary case.
|
|
63
|
+
*/
|
|
64
|
+
export declare function captureAnchor(text: string, range: AnchorRange, docVersion: string): Anchor;
|
|
65
|
+
export type AnchorOutcome = 'unchanged' | 're-attached' | 'flagged' | 'orphaned';
|
|
66
|
+
/**
|
|
67
|
+
* The outcome of re-evaluating a comment's anchor against a new document
|
|
68
|
+
* version (FR-002). Persisted verbatim as the payload of an `anchor-resolved`
|
|
69
|
+
* event; `anchor` is additionally persisted as the payload of the
|
|
70
|
+
* `anchor-recorded` event that accompanies a `re-attached` outcome.
|
|
71
|
+
*/
|
|
72
|
+
export interface AnchorResolution {
|
|
73
|
+
outcome: AnchorOutcome;
|
|
74
|
+
/** The content-repo HEAD SHA this resolution was evaluated against. */
|
|
75
|
+
docVersion: string;
|
|
76
|
+
/** Short, human-readable explanation of why this outcome was reached. */
|
|
77
|
+
reason: string;
|
|
78
|
+
/** Present only when outcome === 're-attached' — the anchor recomputed at the new location. */
|
|
79
|
+
anchor?: Anchor;
|
|
80
|
+
}
|
|
81
|
+
export declare const ANCHOR_RESOLUTION_SCHEMA: z.ZodType<AnchorResolution>;
|