codemirror-obsidian-md 0.0.0-stage → 0.0.0
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 +9 -0
- package/README.md +148 -2
- package/dist/editor/cursor.d.ts +8 -0
- package/dist/editor/cursor.d.ts.map +1 -0
- package/dist/editor/cursor.js +13 -0
- package/dist/editor/cursor.js.map +1 -0
- package/dist/editor/index.d.ts +12 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +11 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/lock.d.ts +46 -0
- package/dist/editor/lock.d.ts.map +1 -0
- package/dist/editor/lock.js +98 -0
- package/dist/editor/lock.js.map +1 -0
- package/dist/editor/mount.d.ts +24 -0
- package/dist/editor/mount.d.ts.map +1 -0
- package/dist/editor/mount.js +34 -0
- package/dist/editor/mount.js.map +1 -0
- package/dist/editor/parse.d.ts +17 -0
- package/dist/editor/parse.d.ts.map +1 -0
- package/dist/editor/parse.js +65 -0
- package/dist/editor/parse.js.map +1 -0
- package/dist/editor/picker.d.ts +40 -0
- package/dist/editor/picker.d.ts.map +1 -0
- package/dist/editor/picker.js +132 -0
- package/dist/editor/picker.js.map +1 -0
- package/dist/editor/providers/callout-embed.d.ts +23 -0
- package/dist/editor/providers/callout-embed.d.ts.map +1 -0
- package/dist/editor/providers/callout-embed.js +229 -0
- package/dist/editor/providers/callout-embed.js.map +1 -0
- package/dist/editor/providers/inline.d.ts +20 -0
- package/dist/editor/providers/inline.d.ts.map +1 -0
- package/dist/editor/providers/inline.js +122 -0
- package/dist/editor/providers/inline.js.map +1 -0
- package/dist/editor/providers/markdown.d.ts +8 -0
- package/dist/editor/providers/markdown.d.ts.map +1 -0
- package/dist/editor/providers/markdown.js +151 -0
- package/dist/editor/providers/markdown.js.map +1 -0
- package/dist/editor/save-flow.d.ts +89 -0
- package/dist/editor/save-flow.d.ts.map +1 -0
- package/dist/editor/save-flow.js +223 -0
- package/dist/editor/save-flow.js.map +1 -0
- package/dist/editor/state.d.ts +39 -0
- package/dist/editor/state.d.ts.map +1 -0
- package/dist/editor/state.js +144 -0
- package/dist/editor/state.js.map +1 -0
- package/dist/editor/types.d.ts +41 -0
- package/dist/editor/types.d.ts.map +1 -0
- package/dist/editor/types.js +2 -0
- package/dist/editor/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/new-note/index.d.ts +3 -0
- package/dist/new-note/index.d.ts.map +1 -0
- package/dist/new-note/index.js +3 -0
- package/dist/new-note/index.js.map +1 -0
- package/dist/new-note/screen.d.ts +21 -0
- package/dist/new-note/screen.d.ts.map +1 -0
- package/dist/new-note/screen.js +100 -0
- package/dist/new-note/screen.js.map +1 -0
- package/dist/new-note/validate.d.ts +23 -0
- package/dist/new-note/validate.d.ts.map +1 -0
- package/dist/new-note/validate.js +65 -0
- package/dist/new-note/validate.js.map +1 -0
- package/dist/storage/client.d.ts +16 -0
- package/dist/storage/client.d.ts.map +1 -0
- package/dist/storage/client.js +57 -0
- package/dist/storage/client.js.map +1 -0
- package/dist/storage/contract.d.ts +14 -0
- package/dist/storage/contract.d.ts.map +1 -0
- package/dist/storage/contract.js +82 -0
- package/dist/storage/contract.js.map +1 -0
- package/dist/storage/errors.d.ts +22 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +29 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/filesystem.d.ts +12 -0
- package/dist/storage/filesystem.d.ts.map +1 -0
- package/dist/storage/filesystem.js +74 -0
- package/dist/storage/filesystem.js.map +1 -0
- package/dist/storage/github-fake.d.ts +60 -0
- package/dist/storage/github-fake.d.ts.map +1 -0
- package/dist/storage/github-fake.js +210 -0
- package/dist/storage/github-fake.js.map +1 -0
- package/dist/storage/github.d.ts +60 -0
- package/dist/storage/github.d.ts.map +1 -0
- package/dist/storage/github.js +210 -0
- package/dist/storage/github.js.map +1 -0
- package/dist/storage/http.d.ts +13 -0
- package/dist/storage/http.d.ts.map +1 -0
- package/dist/storage/http.js +81 -0
- package/dist/storage/http.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +41 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/package.json +108 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adriano Araújo (adrianoaraujodsantos@hotmail.com).
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# codemirror-obsidian-md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CodeMirror 6 editor for Obsidian Notes (in progress, see the project spec).
|
|
4
|
+
|
|
5
|
+
## Storage
|
|
6
|
+
|
|
7
|
+
The editor saves through a `Storage` object you pass in (`read`, `write`, `create`, `delete`, and an optional `lock` capability).
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import type { Storage } from "codemirror-obsidian-md/storage";
|
|
11
|
+
import { createFilesystemStorage } from "codemirror-obsidian-md/storage/filesystem";
|
|
12
|
+
|
|
13
|
+
const storage = createFilesystemStorage({ root: "./content" });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Stale writes throw `StaleShaError`; creating an existing path throws `AlreadyExistsError`.
|
|
17
|
+
|
|
18
|
+
To test your own adapter, run the shared suite from a Vitest file:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { runStorageContract } from "codemirror-obsidian-md/storage/contract";
|
|
22
|
+
|
|
23
|
+
runStorageContract("my adapter", async () => ({ storage: makeStorage() }));
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### GitHub
|
|
27
|
+
|
|
28
|
+
`codemirror-obsidian-md/storage/github` (server-side only, it holds the write credential) commits through the Contents API. Each save, create and delete is one commit: `Update|Create|Delete <path>` plus `Edited-with` and `Edited-by` trailers built from the `editor` you pass (take it from the verified session, never from the client).
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createGithubStorage } from "codemirror-obsidian-md/storage/github";
|
|
32
|
+
|
|
33
|
+
const storage = createGithubStorage({
|
|
34
|
+
repo: "owner/name",
|
|
35
|
+
branch: "main", // default: the repo's default branch
|
|
36
|
+
vaultPath: "content", // default: the repo root
|
|
37
|
+
// A fine-grained PAT, a GitHub App installation, or both (the App wins).
|
|
38
|
+
credential: { token: process.env.GITHUB_TOKEN },
|
|
39
|
+
editor: { name: session.user.name, login: session.user.login },
|
|
40
|
+
deployedCommit: async () => buildSha, // adds `lock`; null when the host has no build commit
|
|
41
|
+
// fetch and baseUrl are injectable (GitHub Enterprise, tests).
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A stale SHA throws `StaleShaError` (with `newer.author` and `newer.time` when GitHub reports them) and an existing path throws `AlreadyExistsError`. `lock.isDeployed(commit)` is true when the deployed commit is identical to or ahead of `commit`.
|
|
46
|
+
|
|
47
|
+
### Browser adapter
|
|
48
|
+
|
|
49
|
+
Mount `createStorageHandler` from `codemirror-obsidian-md/storage/http` in a route handler (check the session inside `getStorage`; throw a `Response` to refuse). In the browser use `createStorageClient({ url: "/api/storage", lock: true })` from `codemirror-obsidian-md/storage/client`. It sends no token; the cookie authenticates and the token stays on the server.
|
|
50
|
+
|
|
51
|
+
### Fake Contents API
|
|
52
|
+
|
|
53
|
+
`createFakeContentsApi` from `codemirror-obsidian-md/storage/github-fake` is an in-memory GitHub with real SHA semantics (409 stale, 422 existing, commits, compare, App token minting). Pass its `fetch` and `baseUrl` to `createGithubStorage`, or wrap its `fetch` in an HTTP server for end-to-end tests. `externalWrite` simulates another person's commit.
|
|
54
|
+
|
|
55
|
+
## Editor
|
|
56
|
+
|
|
57
|
+
The document is the Note's raw Markdown, so saving never rewrites syntax you did not touch (CRLF, a missing trailing newline and odd Frontmatter included). Load it only on your edit route.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createEditor } from "codemirror-obsidian-md/editor";
|
|
61
|
+
|
|
62
|
+
const editor = createEditor({
|
|
63
|
+
parent: document.querySelector("#editor")!,
|
|
64
|
+
doc: markdown,
|
|
65
|
+
providers: [], // Live Preview decoration providers, see below
|
|
66
|
+
idleMs: 250, // re-parse after this much idle time
|
|
67
|
+
onChange: (text) => markDirty(text !== markdown),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await storage.write(path, editor.getText(), sha); // the Save payload is exactly the document
|
|
71
|
+
editor.destroy();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Peer dependencies: `remark-obsidian-md` (the parser, ADR 0014), `@codemirror/state` and `@codemirror/view`.
|
|
75
|
+
|
|
76
|
+
### How it works
|
|
77
|
+
|
|
78
|
+
1. `createNoteParser()` parses the Note with `unified().use(remarkParse).use(remarkGfm).use(remarkMath).use(remarkObsidianMd)` (parse only, no transform, so Comments keep their positions). Frontmatter is skipped. Tables, fenced code, math, Mermaid and reference footnotes (`PLAIN_SOURCE_TYPES`) yield no constructs, so they stay plain source.
|
|
79
|
+
2. Every other MDAST node becomes a `Construct { type, from, to, node }`.
|
|
80
|
+
3. The parse is re-run on the whole Note, debounced to idle. While you type, constructs and decorations are mapped through the edits.
|
|
81
|
+
4. Each `ConstructProvider` whose `types` include a construct's `type` is asked to decorate it.
|
|
82
|
+
|
|
83
|
+
### Extension points
|
|
84
|
+
|
|
85
|
+
Later Live Preview work adds to the core without changing it.
|
|
86
|
+
|
|
87
|
+
- **Decoration providers.** Implement `ConstructProvider` and pass it in `providers`, or register it from any extension with `constructProviders.of(provider)`:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { Decoration } from "@codemirror/view";
|
|
91
|
+
import type { ConstructProvider } from "codemirror-obsidian-md/editor";
|
|
92
|
+
|
|
93
|
+
const highlights: ConstructProvider = {
|
|
94
|
+
name: "highlight",
|
|
95
|
+
types: ["obsidianHighlight"], // MDAST node types: wikiLink, embed, obsidianComment, obsidianFootnote, blockquote, heading, ...
|
|
96
|
+
decorate(construct, { revealed }) {
|
|
97
|
+
if (revealed) return null; // cursor inside: show the raw syntax
|
|
98
|
+
return Decoration.mark({ class: "obsidian-highlight" }).range(construct.from, construct.to);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
A provider returns one `Range<Decoration>`, an array of them, or nothing. Decorations may hide, replace or restyle text, never change it. Widgets are CodeMirror widgets reusing the plugin Stylesheet class names.
|
|
104
|
+
- **Cursor reveal.** `context.revealed` is `isRevealed(state, construct.from, construct.to)`. Call `isRevealed(state, from, to)` yourself for another range (a whole Callout, a line).
|
|
105
|
+
- **Parser.** Pass `parser: (text) => Construct[]` to replace the default, e.g. to add block-level scans such as `scanBlockIds`.
|
|
106
|
+
- **Extra extensions** (autocomplete picker, keymaps, save flow): pass `extensions`, or use `obsidianMd(doc, options)` to compose the core into your own `EditorState`. `reparse(view)` forces a re-parse; `constructsField` and `decorationsField` expose the state.
|
|
107
|
+
|
|
108
|
+
- **Callouts and Embeds.** `calloutEmbed({ types?, resolveAsset? })` (pass it in `extensions`) renders Callouts, Asset Embeds (images) and Note Embeds (chips) with the plugin Stylesheet class names; import `remark-obsidian-md/styles.css` for the colors and icons. The editor gets the `markdown-rendered` class.
|
|
109
|
+
|
|
110
|
+
Decorations are rebuilt when a re-parse lands or the selection moves, and only mapped while typing.
|
|
111
|
+
|
|
112
|
+
Line breaks: the document always splits on `\n`, so a `\r` stays a character and positions equal text offsets. In a Note that is purely CRLF, Enter inserts `\r\n`.
|
|
113
|
+
|
|
114
|
+
### `[[` picker
|
|
115
|
+
|
|
116
|
+
`pickerExtension({ candidates, resolver?, maxResults? })` (pass it in `extensions`) opens a Note picker when `[[` is typed. `candidates` is `NoteCandidate[]` (`{ path, title?, aliases? }`) or a getter, ranked by `rankCandidates`. Choosing one inserts a closed Wiki Link; `wikiLinkTarget` uses the bare name when the injected `Resolver` (or, by default, name uniqueness) reaches that Note, else the path.
|
|
117
|
+
|
|
118
|
+
### Save flow
|
|
119
|
+
|
|
120
|
+
`createSaveFlow({ storage, path, content, sha, getText, confirm })` saves through a `Storage`. Pass `saveFlowExtension(flow)` in `extensions` (dirty tracking, Mod-s), call `flow.attachBeforeUnload()` for the leave-page warning and `mountConflictBanner(flow, parent)` for the conflict banner. `confirm(request)` is the host's dialog (`kind` is `save`, `overwrite` or `reload`; `request.message` is ready to show). A stale SHA commits nothing and sets `state.status` to `conflict`; `flow.overwrite()` refetches the base SHA and re-asks through `confirm`. `flow.subscribe` and `flow.onSaved` (with the `commit` for `Storage.lock`) are the hooks for lock handling.
|
|
121
|
+
|
|
122
|
+
### Lock after Save
|
|
123
|
+
|
|
124
|
+
`createSaveLock(flow, { storage, firstPollMs, intervalMs, timeoutMs })` listens to `flow.onSaved`. When `storage.lock` exists and the Save returned a `commit`, `lock.state.status` becomes `locked`; it polls `storage.lock.isDeployed(commit)` first after `firstPollMs` (1 minute), then every `intervalMs` (5 seconds), and returns to `unlocked` as soon as the deploy is live. After `timeoutMs` (5 minutes) the status is `timeout`: offer `lock.reload()` (Reload from GitHub, through the flow's `confirm`) and `lock.keepWaiting()`. If the Save returns no `commit`, `state.notice` is set once and nothing locks. Without `storage.lock`, and on a conflict, it never locks. Use `lock.subscribe` to disable the editor and `lock.dispose()` on unmount.
|
|
125
|
+
|
|
126
|
+
### Inline Live Preview
|
|
127
|
+
|
|
128
|
+
`inlineProviders` renders Wiki Links, Highlights (with Highlight Color), Comments, inline Footnotes and Block IDs, revealing the raw syntax under the cursor. Block IDs are not parse nodes, so wrap the parser with `blockIdParser()`:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
createEditor({ parent, doc, providers: inlineProviders, parser: blockIdParser() });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## New Note screen
|
|
135
|
+
|
|
136
|
+
`codemirror-obsidian-md/new-note` is a framework-agnostic DOM component (the package has no React) plus the pure validation it uses. `Storage` cannot list files, so the host passes the Vault-relative paths of existing Notes.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createNewNoteScreen } from "codemirror-obsidian-md/new-note";
|
|
140
|
+
|
|
141
|
+
const screen = createNewNoteScreen({
|
|
142
|
+
parent: document.querySelector("#new-note")!,
|
|
143
|
+
existingPaths, // e.g. ["Index.md", "Geografia/Seba.md"]
|
|
144
|
+
onCreate: (path) => storage.create(path, ""), // Vault-relative, ends in .md
|
|
145
|
+
onCancel: () => history.back(),
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The folder field offers existing folders and accepts a typed nested one; a folder exists only once a Note is created in it. `resolveNewNote({ folder, name, existing })` returns `{ ok: true, path }` or `{ ok: false, error }` (empty name, leading `/`, `..` segment, characters GitHub paths reject, existing path, case-only difference). `listFolders(paths)` derives the picker list.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EditorState } from "@codemirror/state";
|
|
2
|
+
/**
|
|
3
|
+
* The cursor-reveal helper: true when any selection range touches
|
|
4
|
+
* `[from, to]` (ends included), so a construct under the cursor shows its raw
|
|
5
|
+
* syntax. Providers call this to decide between the rendered and raw form.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isRevealed(state: EditorState, from: number, to: number): boolean;
|
|
8
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/editor/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAKT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cursor-reveal helper: true when any selection range touches
|
|
3
|
+
* `[from, to]` (ends included), so a construct under the cursor shows its raw
|
|
4
|
+
* syntax. Providers call this to decide between the rendered and raw form.
|
|
5
|
+
*/
|
|
6
|
+
export function isRevealed(state, from, to) {
|
|
7
|
+
for (const range of state.selection.ranges) {
|
|
8
|
+
if (range.from <= to && range.to >= from)
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/editor/cursor.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,KAAkB,EAClB,IAAY,EACZ,EAAU;IAEV,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE,IAAI,KAAK,CAAC,EAAE,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { isRevealed } from "./cursor.js";
|
|
2
|
+
export * from "./lock.js";
|
|
3
|
+
export { type CreateEditorOptions, createEditor, type EditorHandle, } from "./mount.js";
|
|
4
|
+
export { createNoteParser, frontmatterLength, PLAIN_SOURCE_TYPES, } from "./parse.js";
|
|
5
|
+
export { type NoteCandidate, type PickerOptions, pickerExtension, rankCandidates, wikiLinkTarget, } from "./picker.js";
|
|
6
|
+
export { type CalloutEmbedOptions, calloutEmbed, } from "./providers/callout-embed.js";
|
|
7
|
+
export { blockIdParser, blockIdProvider, commentProvider, footnoteProvider, highlightProvider, inlineProviders, wikiLinkProvider, } from "./providers/inline.js";
|
|
8
|
+
export { markdownProvider } from "./providers/markdown.js";
|
|
9
|
+
export * from "./save-flow.js";
|
|
10
|
+
export { constructProviders, constructsField, DEFAULT_IDLE_MS, decorationsField, isCrlf, type ObsidianMdOptions, obsidianMd, reparse, } from "./state.js";
|
|
11
|
+
export type { Construct, ConstructProvider, DecorationContext, NoteParser, ProviderResult, } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,cAAc,WAAW,CAAC;AAC1B,OAAO,EACL,KAAK,mBAAmB,EACxB,YAAY,EACZ,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,mBAAmB,EACxB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,MAAM,EACN,KAAK,iBAAiB,EACtB,UAAU,EACV,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { isRevealed } from "./cursor.js";
|
|
2
|
+
export * from "./lock.js";
|
|
3
|
+
export { createEditor, } from "./mount.js";
|
|
4
|
+
export { createNoteParser, frontmatterLength, PLAIN_SOURCE_TYPES, } from "./parse.js";
|
|
5
|
+
export { pickerExtension, rankCandidates, wikiLinkTarget, } from "./picker.js";
|
|
6
|
+
export { calloutEmbed, } from "./providers/callout-embed.js";
|
|
7
|
+
export { blockIdParser, blockIdProvider, commentProvider, footnoteProvider, highlightProvider, inlineProviders, wikiLinkProvider, } from "./providers/inline.js";
|
|
8
|
+
export { markdownProvider } from "./providers/markdown.js";
|
|
9
|
+
export * from "./save-flow.js";
|
|
10
|
+
export { constructProviders, constructsField, DEFAULT_IDLE_MS, decorationsField, isCrlf, obsidianMd, reparse, } from "./state.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,cAAc,WAAW,CAAC;AAC1B,OAAO,EAEL,YAAY,GAEb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,YAAY,GACb,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,MAAM,EAEN,UAAU,EACV,OAAO,GACR,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Storage } from "../storage/types.js";
|
|
2
|
+
import type { SaveFlow } from "./save-flow.js";
|
|
3
|
+
export type SaveLockStatus = "unlocked" | "locked" | "timeout";
|
|
4
|
+
export interface SaveLockState {
|
|
5
|
+
/**
|
|
6
|
+
* `locked`: a Save is waiting for its deploy, so the Note must not be
|
|
7
|
+
* edited. `timeout`: the deploy did not appear in time; offer `reload()`
|
|
8
|
+
* and `keepWaiting()`.
|
|
9
|
+
*/
|
|
10
|
+
status: SaveLockStatus;
|
|
11
|
+
/** One-time message, set when the host exposes no build commit. */
|
|
12
|
+
notice: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface SaveLockOptions {
|
|
15
|
+
storage: Storage;
|
|
16
|
+
/** Delay before the first poll. Default 1 minute. */
|
|
17
|
+
firstPollMs?: number;
|
|
18
|
+
/** Delay between later polls. Default 5 seconds. */
|
|
19
|
+
intervalMs?: number;
|
|
20
|
+
/** Total wait, from the Save, before the timeout. Default 5 minutes. */
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Start locked on a commit made before the editor opened (for example the
|
|
24
|
+
* commit that created the Note), as if a Save had just produced it. Needs
|
|
25
|
+
* `storage.lock`; without it nothing locks.
|
|
26
|
+
*/
|
|
27
|
+
lockedCommit?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SaveLock {
|
|
30
|
+
readonly state: SaveLockState;
|
|
31
|
+
subscribe(listener: (state: SaveLockState) => void): () => void;
|
|
32
|
+
/** Timeout only: wait another `timeoutMs`. */
|
|
33
|
+
keepWaiting(): void;
|
|
34
|
+
/** Reload the Note from GitHub (through the flow's confirm), then unlock. */
|
|
35
|
+
reload(): Promise<boolean>;
|
|
36
|
+
dismissNotice(): void;
|
|
37
|
+
dispose(): void;
|
|
38
|
+
}
|
|
39
|
+
export declare const NO_COMMIT_NOTICE = "Your host does not expose the build commit, so the editor cannot tell when your change is live. The Note is not locked.";
|
|
40
|
+
/**
|
|
41
|
+
* Locks the Note after a successful Save until `storage.lock` reports the
|
|
42
|
+
* deploy is live. Wired through `flow.onSaved`; conflicts and failed saves
|
|
43
|
+
* never reach it, and a storage without `lock` is never locked.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createSaveLock(flow: SaveFlow, options: SaveLockOptions): SaveLock;
|
|
46
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/editor/lock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,MAAM,EAAE,cAAc,CAAC;IACvB,mEAAmE;IACnE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAChE,8CAA8C;IAC9C,WAAW,IAAI,IAAI,CAAC;IACpB,6EAA6E;IAC7E,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,aAAa,IAAI,IAAI,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,gBAAgB,4HAC8F,CAAC;AAE5H;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,eAAe,GACvB,QAAQ,CAyFV"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const NO_COMMIT_NOTICE = "Your host does not expose the build commit, so the editor cannot tell when your change is live. The Note is not locked.";
|
|
2
|
+
/**
|
|
3
|
+
* Locks the Note after a successful Save until `storage.lock` reports the
|
|
4
|
+
* deploy is live. Wired through `flow.onSaved`; conflicts and failed saves
|
|
5
|
+
* never reach it, and a storage without `lock` is never locked.
|
|
6
|
+
*/
|
|
7
|
+
export function createSaveLock(flow, options) {
|
|
8
|
+
const { storage } = options;
|
|
9
|
+
const firstPollMs = options.firstPollMs ?? 60_000;
|
|
10
|
+
const intervalMs = options.intervalMs ?? 5_000;
|
|
11
|
+
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
12
|
+
let state = { status: "unlocked", notice: null };
|
|
13
|
+
let noticeShown = false;
|
|
14
|
+
let commit = "";
|
|
15
|
+
let timer;
|
|
16
|
+
let generation = 0;
|
|
17
|
+
const listeners = new Set();
|
|
18
|
+
function set(patch) {
|
|
19
|
+
state = { ...state, ...patch };
|
|
20
|
+
for (const l of [...listeners])
|
|
21
|
+
l(state);
|
|
22
|
+
}
|
|
23
|
+
function stop() {
|
|
24
|
+
generation++;
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
timer = undefined;
|
|
27
|
+
}
|
|
28
|
+
function start(newCommit) {
|
|
29
|
+
stop();
|
|
30
|
+
commit = newCommit;
|
|
31
|
+
const gen = generation;
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
set({ status: "locked" });
|
|
34
|
+
const poll = async () => {
|
|
35
|
+
let deployed = false;
|
|
36
|
+
try {
|
|
37
|
+
deployed = (await storage.lock?.isDeployed(commit)) === true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A failed check means "not yet"; keep polling until the timeout.
|
|
41
|
+
}
|
|
42
|
+
if (gen !== generation)
|
|
43
|
+
return;
|
|
44
|
+
if (deployed)
|
|
45
|
+
return set({ status: "unlocked" });
|
|
46
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
47
|
+
if (remaining <= 0)
|
|
48
|
+
return set({ status: "timeout" });
|
|
49
|
+
timer = setTimeout(poll, Math.min(intervalMs, remaining));
|
|
50
|
+
};
|
|
51
|
+
timer = setTimeout(poll, Math.min(firstPollMs, timeoutMs));
|
|
52
|
+
}
|
|
53
|
+
if (options.lockedCommit && storage.lock)
|
|
54
|
+
start(options.lockedCommit);
|
|
55
|
+
const offSaved = flow.onSaved((result) => {
|
|
56
|
+
if (!storage.lock)
|
|
57
|
+
return;
|
|
58
|
+
if (result.commit === undefined) {
|
|
59
|
+
if (!noticeShown) {
|
|
60
|
+
noticeShown = true;
|
|
61
|
+
set({ notice: NO_COMMIT_NOTICE });
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
start(result.commit);
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
get state() {
|
|
69
|
+
return state;
|
|
70
|
+
},
|
|
71
|
+
subscribe(listener) {
|
|
72
|
+
listeners.add(listener);
|
|
73
|
+
return () => listeners.delete(listener);
|
|
74
|
+
},
|
|
75
|
+
keepWaiting() {
|
|
76
|
+
if (state.status === "timeout")
|
|
77
|
+
start(commit);
|
|
78
|
+
},
|
|
79
|
+
async reload() {
|
|
80
|
+
const ok = await flow.reload();
|
|
81
|
+
if (ok) {
|
|
82
|
+
stop();
|
|
83
|
+
set({ status: "unlocked" });
|
|
84
|
+
}
|
|
85
|
+
return ok;
|
|
86
|
+
},
|
|
87
|
+
dismissNotice() {
|
|
88
|
+
if (state.notice !== null)
|
|
89
|
+
set({ notice: null });
|
|
90
|
+
},
|
|
91
|
+
dispose() {
|
|
92
|
+
stop();
|
|
93
|
+
offSaved();
|
|
94
|
+
listeners.clear();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/editor/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Extension } from "@codemirror/state";
|
|
2
|
+
import { EditorView } from "@codemirror/view";
|
|
3
|
+
import { type ObsidianMdOptions } from "./state.js";
|
|
4
|
+
export interface CreateEditorOptions extends ObsidianMdOptions {
|
|
5
|
+
/** Element the editor mounts into. */
|
|
6
|
+
parent: HTMLElement;
|
|
7
|
+
/** The Note's raw Markdown. */
|
|
8
|
+
doc: string;
|
|
9
|
+
/** Extra extensions, appended after the core ones. */
|
|
10
|
+
extensions?: readonly Extension[];
|
|
11
|
+
/** Called with the full text after every document change. */
|
|
12
|
+
onChange?: (text: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface EditorHandle {
|
|
15
|
+
readonly view: EditorView;
|
|
16
|
+
/** The Save payload: exactly the document text, nothing re-serialized. */
|
|
17
|
+
getText(): string;
|
|
18
|
+
/** Re-parse now instead of waiting for idle. */
|
|
19
|
+
reparse(): void;
|
|
20
|
+
destroy(): void;
|
|
21
|
+
}
|
|
22
|
+
/** Mounts the editor on a Note's raw Markdown. */
|
|
23
|
+
export declare function createEditor(options: CreateEditorOptions): EditorHandle;
|
|
24
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/editor/mount.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAU,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,KAAK,iBAAiB,EAAuB,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,sCAAsC;IACtC,MAAM,EAAE,WAAW,CAAC;IACpB,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,0EAA0E;IAC1E,OAAO,IAAI,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,IAAI,IAAI,CAAC;IAChB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CA0BvE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
2
|
+
import { EditorState } from "@codemirror/state";
|
|
3
|
+
import { EditorView, keymap } from "@codemirror/view";
|
|
4
|
+
import { obsidianMd, reparse } from "./state.js";
|
|
5
|
+
/** Mounts the editor on a Note's raw Markdown. */
|
|
6
|
+
export function createEditor(options) {
|
|
7
|
+
const { parent, doc, onChange } = options;
|
|
8
|
+
const view = new EditorView({
|
|
9
|
+
parent,
|
|
10
|
+
state: EditorState.create({
|
|
11
|
+
doc,
|
|
12
|
+
extensions: [
|
|
13
|
+
history(),
|
|
14
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
15
|
+
EditorView.lineWrapping,
|
|
16
|
+
obsidianMd(doc, options),
|
|
17
|
+
onChange
|
|
18
|
+
? EditorView.updateListener.of((u) => {
|
|
19
|
+
if (u.docChanged)
|
|
20
|
+
onChange(u.state.doc.toString());
|
|
21
|
+
})
|
|
22
|
+
: [],
|
|
23
|
+
options.extensions ?? [],
|
|
24
|
+
],
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
view,
|
|
29
|
+
getText: () => view.state.doc.toString(),
|
|
30
|
+
reparse: () => reparse(view),
|
|
31
|
+
destroy: () => view.destroy(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../../src/editor/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAA0B,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAsBzE,kDAAkD;AAClD,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;QAC1B,MAAM;QACN,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC;YACxB,GAAG;YACH,UAAU,EAAE;gBACV,OAAO,EAAE;gBACT,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,aAAa,EAAE,GAAG,aAAa,CAAC,CAAC;gBAC/C,UAAU,CAAC,YAAY;gBACvB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;gBACxB,QAAQ;oBACN,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;wBACjC,IAAI,CAAC,CAAC,UAAU;4BAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;oBACrD,CAAC,CAAC;oBACJ,CAAC,CAAC,EAAE;gBACN,OAAO,CAAC,UAAU,IAAI,EAAE;aACzB;SACF,CAAC;KACH,CAAC,CAAC;IACH,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE;QACxC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;KAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NoteParser } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Node types the MVP shows as plain source (tables, fenced code, math,
|
|
4
|
+
* Mermaid, reference footnotes). They are never emitted and never descended
|
|
5
|
+
* into, so nothing inside them is decorated.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PLAIN_SOURCE_TYPES: ReadonlySet<string>;
|
|
8
|
+
/** Length of the Frontmatter block at the top of `text` (0 if none). */
|
|
9
|
+
export declare function frontmatterLength(text: string): number;
|
|
10
|
+
/**
|
|
11
|
+
* Builds the parser from the plugin pipeline (ADR 0014): remark-parse,
|
|
12
|
+
* remark-gfm and remark-obsidian-md. Only `parse` runs, never the plugin's
|
|
13
|
+
* transform, so the tree keeps every construct (Comments included) at its
|
|
14
|
+
* source position.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createNoteParser(): NoteParser;
|
|
17
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/editor/parse.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAQjD,CAAC;AAKH,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAoB7C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import remarkGfm from "remark-gfm";
|
|
2
|
+
import remarkMath from "remark-math";
|
|
3
|
+
import { remarkObsidianMd } from "remark-obsidian-md/syntax";
|
|
4
|
+
import remarkParse from "remark-parse";
|
|
5
|
+
import { unified } from "unified";
|
|
6
|
+
/**
|
|
7
|
+
* Node types the MVP shows as plain source (tables, fenced code, math,
|
|
8
|
+
* Mermaid, reference footnotes). They are never emitted and never descended
|
|
9
|
+
* into, so nothing inside them is decorated.
|
|
10
|
+
*/
|
|
11
|
+
export const PLAIN_SOURCE_TYPES = new Set([
|
|
12
|
+
"table",
|
|
13
|
+
"code",
|
|
14
|
+
"math",
|
|
15
|
+
"inlineMath",
|
|
16
|
+
"footnoteDefinition",
|
|
17
|
+
"footnoteReference",
|
|
18
|
+
"yaml",
|
|
19
|
+
]);
|
|
20
|
+
const FRONTMATTER = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/;
|
|
21
|
+
/** Length of the Frontmatter block at the top of `text` (0 if none). */
|
|
22
|
+
export function frontmatterLength(text) {
|
|
23
|
+
return FRONTMATTER.exec(text)?.[0].length ?? 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds the parser from the plugin pipeline (ADR 0014): remark-parse,
|
|
27
|
+
* remark-gfm and remark-obsidian-md. Only `parse` runs, never the plugin's
|
|
28
|
+
* transform, so the tree keeps every construct (Comments included) at its
|
|
29
|
+
* source position.
|
|
30
|
+
*/
|
|
31
|
+
export function createNoteParser() {
|
|
32
|
+
const processor = unified()
|
|
33
|
+
.use(remarkParse)
|
|
34
|
+
.use(remarkGfm)
|
|
35
|
+
.use(remarkMath)
|
|
36
|
+
.use(remarkObsidianMd);
|
|
37
|
+
return (text) => {
|
|
38
|
+
// Frontmatter is plain source; parsing it would turn `---` into rules.
|
|
39
|
+
const offset = frontmatterLength(text);
|
|
40
|
+
let tree;
|
|
41
|
+
try {
|
|
42
|
+
tree = processor.parse(text.slice(offset));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const out = [];
|
|
48
|
+
walk(tree, offset, out);
|
|
49
|
+
return out;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function walk(node, offset, out) {
|
|
53
|
+
if (PLAIN_SOURCE_TYPES.has(node.type))
|
|
54
|
+
return;
|
|
55
|
+
const start = node.position?.start.offset;
|
|
56
|
+
const end = node.position?.end.offset;
|
|
57
|
+
if (node.type !== "root" && start !== undefined && end !== undefined) {
|
|
58
|
+
out.push({ type: node.type, from: start + offset, to: end + offset, node });
|
|
59
|
+
}
|
|
60
|
+
if ("children" in node) {
|
|
61
|
+
for (const child of node.children)
|
|
62
|
+
walk(child, offset, out);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/editor/parse.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIlC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC;IAC7D,OAAO;IACP,MAAM;IACN,MAAM;IACN,YAAY;IACZ,oBAAoB;IACpB,mBAAmB;IACnB,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,WAAW,GACf,kEAAkE,CAAC;AAErE,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,SAAS,GAAG,OAAO,EAAE;SACxB,GAAG,CAAC,WAAW,CAAC;SAChB,GAAG,CAAC,SAAS,CAAC;SACd,GAAG,CAAC,UAAU,CAAC;SACf,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAEzB,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,uEAAuE;QACvE,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,IAAW,CAAC;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,IAAW,EAAE,MAAc,EAAE,GAAgB;IACzD,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC;IACtC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACrE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACvB,KAAK,MAAM,KAAK,IAAK,IAAe,CAAC,QAAQ;YAC3C,IAAI,CAAC,KAAc,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Extension } from "@codemirror/state";
|
|
2
|
+
import type { Resolver } from "remark-obsidian-md";
|
|
3
|
+
/** A Note the host offers in the picker. */
|
|
4
|
+
export interface NoteCandidate {
|
|
5
|
+
/** Vault path, e.g. `Projects/Roadmap.md`. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Display name. Default: the file name without its extension. */
|
|
8
|
+
title?: string;
|
|
9
|
+
/** Alias names the Note can also be found by. */
|
|
10
|
+
aliases?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface PickerOptions {
|
|
13
|
+
/** The Vault's Notes, or a getter read on every open so props can change. */
|
|
14
|
+
candidates: readonly NoteCandidate[] | (() => readonly NoteCandidate[]);
|
|
15
|
+
/**
|
|
16
|
+
* Decides which target reaches a Note, so the inserted link resolves the
|
|
17
|
+
* same as on the Wiki (ADR 0003). Default: the bare name unless another
|
|
18
|
+
* candidate shares it, else the path.
|
|
19
|
+
*/
|
|
20
|
+
resolver?: Resolver;
|
|
21
|
+
/** Maximum options shown. Default 100. */
|
|
22
|
+
maxResults?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ranks Notes by the typed text: exact title, title prefix, word start,
|
|
26
|
+
* substring, Alias, path, then subsequence. Ties: shorter title, then path.
|
|
27
|
+
* An empty query returns every candidate by path.
|
|
28
|
+
*/
|
|
29
|
+
export declare function rankCandidates(candidates: readonly NoteCandidate[], query: string): NoteCandidate[];
|
|
30
|
+
/**
|
|
31
|
+
* The Wiki Link target that reaches `candidate`: the bare name when it
|
|
32
|
+
* resolves to the Note, otherwise the path (both without `.md`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function wikiLinkTarget(candidate: NoteCandidate, all: readonly NoteCandidate[], resolver?: Resolver): string;
|
|
35
|
+
/**
|
|
36
|
+
* The `[[` picker: typing `[[` opens a list of the host's Notes ranked by the
|
|
37
|
+
* text typed after it; choosing one inserts a closed Wiki Link.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pickerExtension(options: PickerOptions): Extension;
|
|
40
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/editor/picker.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,UAAU,EAAE,SAAS,aAAa,EAAE,GAAG,CAAC,MAAM,SAAS,aAAa,EAAE,CAAC,CAAC;IACxE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA0ED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,KAAK,EAAE,MAAM,GACZ,aAAa,EAAE,CAEjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,aAAa,EACxB,GAAG,EAAE,SAAS,aAAa,EAAE,EAC7B,QAAQ,CAAC,EAAE,QAAQ,GAClB,MAAM,CAgBR;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CA6BjE"}
|