@lapis-notes/api 0.1.4 → 0.2.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.
Files changed (188) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/README.md +21 -2
  3. package/dist/__tests__/fixtures/indexed-mutations.json +6862 -0
  4. package/dist/__tests__/fixtures/memory-storage.json +309 -0
  5. package/dist/__tests__/fixtures/native-facade-contract.json +3224 -0
  6. package/dist/__tests__/fixtures/projection-query.json +195 -0
  7. package/dist/__tests__/fixtures/projection-values.json +60 -0
  8. package/dist/__tests__/fixtures/search-preparation.json +1513 -0
  9. package/dist/__tests__/fixtures/search-retrieval.json +15409 -0
  10. package/dist/__tests__/fixtures/search-semantic-model.json +3691 -0
  11. package/dist/__tests__/fixtures/search-semantic-retrieval.json +6603 -0
  12. package/dist/__tests__/fixtures/search-storage.json +409 -0
  13. package/dist/__tests__/fixtures/task-projection.json +428 -0
  14. package/dist/__tests__/indexed-mutation-support.js +262 -0
  15. package/dist/__tests__/projection-query-support.js +40 -0
  16. package/dist/__tests__/search-retrieval-support.js +47 -0
  17. package/dist/app-database-host.d.ts +1 -0
  18. package/dist/app-shell-plugin-persistence.d.ts +3 -2
  19. package/dist/app-shell-plugin-persistence.js +37 -3
  20. package/dist/cache.svelte.d.ts +8 -1
  21. package/dist/cache.svelte.js +103 -5
  22. package/dist/command.svelte.js +29 -2
  23. package/dist/components/editor/editor.css +4 -0
  24. package/dist/components/editor/editor.svelte +21 -9
  25. package/dist/components/editor/embedded-editor-surface.js +3 -1
  26. package/dist/components/editor/inline-title-alignment.d.ts +3 -0
  27. package/dist/components/editor/inline-title-alignment.js +57 -0
  28. package/dist/configuration.svelte.d.ts +270 -206
  29. package/dist/configuration.svelte.js +161 -86
  30. package/dist/context.svelte.d.ts +21 -3
  31. package/dist/context.svelte.js +63 -20
  32. package/dist/diagnostics.d.ts +8 -0
  33. package/dist/diagnostics.js +6 -0
  34. package/dist/editor-save-context.d.ts +12 -1
  35. package/dist/editor-save-context.js +32 -1
  36. package/dist/editor-view-registry.d.ts +10 -0
  37. package/dist/editor-view-registry.js +44 -4
  38. package/dist/editor.svelte.js +128 -37
  39. package/dist/embed-registry.d.ts +10 -0
  40. package/dist/embed-registry.js +15 -0
  41. package/dist/file-editing-failures.d.ts +2 -0
  42. package/dist/file-editing-failures.js +29 -0
  43. package/dist/file-editing-native-issues.d.ts +19 -0
  44. package/dist/file-editing-native-issues.js +65 -0
  45. package/dist/file-editing-sessions.d.ts +56 -8
  46. package/dist/file-editing-sessions.js +185 -15
  47. package/dist/file-manager.d.ts +8 -1
  48. package/dist/file-manager.js +37 -3
  49. package/dist/file-state-store.d.ts +5 -0
  50. package/dist/file-state-store.js +34 -0
  51. package/dist/file-text-conflicts.d.ts +25 -0
  52. package/dist/file-text-conflicts.js +35 -0
  53. package/dist/file-text-mutation.d.ts +13 -0
  54. package/dist/file-text-mutation.js +100 -0
  55. package/dist/file-text-persistence.d.ts +36 -0
  56. package/dist/file-text-persistence.js +234 -0
  57. package/dist/file-text-state-writer.d.ts +34 -0
  58. package/dist/file-text-state-writer.js +183 -0
  59. package/dist/files-configuration.d.ts +20 -0
  60. package/dist/files-configuration.js +47 -0
  61. package/dist/generated/plugin-host-modules.schema.generated.d.ts +87 -37
  62. package/dist/generated/plugin-host-modules.schema.generated.js +102 -37
  63. package/dist/glob.d.ts +3 -0
  64. package/dist/glob.js +2 -1
  65. package/dist/icons/changes.d.ts +3 -0
  66. package/dist/icons/changes.js +16 -0
  67. package/dist/icons/index.d.ts +5 -1
  68. package/dist/icons/index.js +44 -3
  69. package/dist/icons/registry.d.ts +35 -0
  70. package/dist/icons/registry.js +154 -0
  71. package/dist/icons/safe-svg.d.ts +2 -0
  72. package/dist/icons/safe-svg.js +44 -0
  73. package/dist/index.d.ts +5 -1
  74. package/dist/index.js +3 -1
  75. package/dist/lapis-extension.d.ts +6 -2
  76. package/dist/lapis-extension.js +2 -0
  77. package/dist/metadata.svelte.js +45 -2
  78. package/dist/native-background-indexing.d.ts +48 -0
  79. package/dist/native-background-indexing.js +208 -0
  80. package/dist/native-database-protocol.d.ts +29 -0
  81. package/dist/native-database-protocol.js +1 -0
  82. package/dist/native-editing-intent-lease.d.ts +17 -0
  83. package/dist/native-editing-intent-lease.js +188 -0
  84. package/dist/native-editing-intents-protocol.d.ts +70 -0
  85. package/dist/native-editing-intents-protocol.js +1 -0
  86. package/dist/native-editing-intents.d.ts +8 -0
  87. package/dist/native-editing-intents.js +1 -0
  88. package/dist/native-editing-problems.d.ts +3 -0
  89. package/dist/native-editing-problems.js +180 -0
  90. package/dist/native-editing-recovery.d.ts +24 -0
  91. package/dist/native-editing-recovery.js +198 -0
  92. package/dist/native-editing-session.d.ts +51 -0
  93. package/dist/native-editing-session.js +251 -0
  94. package/dist/native-history-index-protocol.d.ts +80 -0
  95. package/dist/native-history-index-protocol.js +1 -0
  96. package/dist/native-history-index.d.ts +6 -0
  97. package/dist/native-history-index.js +1 -0
  98. package/dist/native-indexing-protocol.d.ts +63 -0
  99. package/dist/native-indexing-protocol.js +1 -0
  100. package/dist/native-participant-protocol.d.ts +67 -0
  101. package/dist/native-participant-protocol.js +3 -0
  102. package/dist/native-queue-protocol.d.ts +73 -0
  103. package/dist/native-queue-protocol.js +3 -0
  104. package/dist/native-tasks-protocol.d.ts +264 -0
  105. package/dist/native-tasks-protocol.js +1 -0
  106. package/dist/native-tasks.d.ts +35 -0
  107. package/dist/native-tasks.js +79 -0
  108. package/dist/native-turn-protocol.d.ts +81 -0
  109. package/dist/native-turn-protocol.js +1 -0
  110. package/dist/native-turns.d.ts +29 -0
  111. package/dist/native-turns.js +2 -0
  112. package/dist/plugin-asset-server.js +3 -2
  113. package/dist/plugin-dependency-resolver.d.ts +1 -1
  114. package/dist/plugin-distribution/compatibility.d.ts +1 -1
  115. package/dist/plugin-distribution/compatibility.js +10 -5
  116. package/dist/plugin-distribution/installed-plugin-state.js +2 -1
  117. package/dist/plugin-distribution/installer.d.ts +6 -0
  118. package/dist/plugin-distribution/installer.js +90 -37
  119. package/dist/plugin-distribution/manager.d.ts +4 -1
  120. package/dist/plugin-distribution/manager.js +12 -5
  121. package/dist/plugin-distribution/registry-cache.js +2 -1
  122. package/dist/plugin-distribution/registry-source-state.js +2 -1
  123. package/dist/plugin-distribution/schemas.d.ts +26 -8
  124. package/dist/plugin-distribution/schemas.js +2 -1
  125. package/dist/plugin-distribution/types.d.ts +4 -0
  126. package/dist/plugin-manager.d.ts +20 -4
  127. package/dist/plugin-manager.js +228 -64
  128. package/dist/plugin-runtime-entry.d.ts +1 -1
  129. package/dist/plugin-runtime-entry.js +16 -0
  130. package/dist/plugin.d.ts +18 -2
  131. package/dist/plugin.js +93 -39
  132. package/dist/registry-schema/catalog-v1.d.ts +60 -9
  133. package/dist/registry-schema/catalog-v1.js +2 -1
  134. package/dist/registry-schema/catalog-v2.d.ts +60 -9
  135. package/dist/registry-schema/catalog-v2.js +1 -1
  136. package/dist/search-result-provider.d.ts +95 -0
  137. package/dist/search-result-provider.js +42 -0
  138. package/dist/status-bar.svelte.d.ts +26 -20
  139. package/dist/status-bar.svelte.js +1 -0
  140. package/dist/storage/app-database.d.ts +6 -2
  141. package/dist/storage/app-database.js +41 -34
  142. package/dist/storage/desktop-native.d.ts +5 -3
  143. package/dist/storage/desktop-native.js +59 -6
  144. package/dist/storage/directory-watcher.d.ts +27 -0
  145. package/dist/storage/directory-watcher.js +201 -0
  146. package/dist/storage/fs.d.ts +14 -27
  147. package/dist/storage/fs.js +2 -168
  148. package/dist/storage/index-projection.js +33 -15
  149. package/dist/storage/memory-vault-adapter.d.ts +2 -0
  150. package/dist/storage/memory-vault-adapter.js +14 -1
  151. package/dist/storage/path.d.ts +6 -2
  152. package/dist/storage/path.js +8 -2
  153. package/dist/storage/projection-query-candidates.d.ts +3 -0
  154. package/dist/storage/projection-query-candidates.js +69 -0
  155. package/dist/storage/remote-app-database.d.ts +2 -2
  156. package/dist/storage/remote-app-database.js +1 -1
  157. package/dist/storage/runtime.d.ts +3 -0
  158. package/dist/storage/runtime.js +3 -0
  159. package/dist/storage/search-embedding-record.d.ts +3 -0
  160. package/dist/storage/search-embedding-record.js +18 -0
  161. package/dist/storage/turso-app-database.d.ts +2 -0
  162. package/dist/storage/turso-app-database.js +67 -30
  163. package/dist/storage/vault-listing.d.ts +3 -0
  164. package/dist/storage/vault-listing.js +36 -0
  165. package/dist/storage/vault-reconciliation.d.ts +19 -0
  166. package/dist/storage/vault-reconciliation.js +137 -0
  167. package/dist/storage/vault-session.d.ts +8 -0
  168. package/dist/storage/vault-session.js +10 -2
  169. package/dist/storage/vault.svelte.d.ts +53 -4
  170. package/dist/storage/vault.svelte.js +223 -87
  171. package/dist/sync-host.d.ts +12 -3
  172. package/dist/sync-host.js +5 -1
  173. package/dist/vault-change-views.d.ts +4 -0
  174. package/dist/vault-change-views.js +43 -0
  175. package/dist/vault-changes.d.ts +37 -0
  176. package/dist/vault-changes.js +228 -0
  177. package/dist/view.svelte.d.ts +182 -149
  178. package/dist/view.svelte.js +11 -2
  179. package/dist/workspace-close-guard.d.ts +3 -0
  180. package/dist/workspace-close-guard.js +35 -0
  181. package/dist/workspace-host-internal.js +10 -2
  182. package/dist/workspace-layout-normalizer.d.ts +4 -2
  183. package/dist/workspace-layout-normalizer.js +5 -5
  184. package/dist/workspace-view-document.d.ts +3 -0
  185. package/dist/workspace-view-document.js +12 -0
  186. package/dist/workspace.svelte.d.ts +13 -2
  187. package/dist/workspace.svelte.js +371 -82
  188. package/package.json +45 -8
@@ -3,7 +3,10 @@ export interface EditorSaveSnapshot {
3
3
  sequence: number;
4
4
  documentGeneration: number;
5
5
  }
6
- /** One document's queued saves and failures; the editor supplies shared serialization. */
6
+ /**
7
+ * One document's queued saves and failures; the editor supplies shared
8
+ * serialization.
9
+ */
7
10
  export declare class EditorSaveContext {
8
11
  #private;
9
12
  private readonly options;
@@ -15,6 +18,14 @@ export declare class EditorSaveContext {
15
18
  released(): void;
16
19
  });
17
20
  get dirty(): boolean;
21
+ get latest(): EditorSaveSnapshot | undefined;
22
+ /** A protected explicit recovery may acknowledge the latest retained snapshot. */
23
+ accept(snapshot: EditorSaveSnapshot): void;
24
+ /**
25
+ * Discard only after the recovery owner has revalidated the external
26
+ * snapshot.
27
+ */
28
+ discard(): void;
18
29
  queue(snapshot: EditorSaveSnapshot, explicit?: boolean): Promise<void> | undefined;
19
30
  flushSave(): Promise<void> | undefined;
20
31
  cancelSave(): void;
@@ -1,5 +1,8 @@
1
1
  import debounce from "lodash-es/debounce";
2
- /** One document's queued saves and failures; the editor supplies shared serialization. */
2
+ /**
3
+ * One document's queued saves and failures; the editor supplies shared
4
+ * serialization.
5
+ */
3
6
  export class EditorSaveContext {
4
7
  options;
5
8
  #latest;
@@ -28,6 +31,34 @@ export class EditorSaveContext {
28
31
  !!this.#failure ||
29
32
  this.#unverified);
30
33
  }
34
+ get latest() {
35
+ return this.#latest;
36
+ }
37
+ /** A protected explicit recovery may acknowledge the latest retained snapshot. */
38
+ accept(snapshot) {
39
+ this.#reset();
40
+ this.#completed = Math.max(this.#completed, snapshot.sequence);
41
+ this.options.saved(snapshot);
42
+ this.#releaseIfClean();
43
+ }
44
+ /**
45
+ * Discard only after the recovery owner has revalidated the external
46
+ * snapshot.
47
+ */
48
+ discard() {
49
+ this.#reset();
50
+ this.#completed = Math.max(this.#completed, this.#latest?.sequence ?? 0);
51
+ this.#latest = undefined;
52
+ this.#releaseIfClean();
53
+ }
54
+ #reset() {
55
+ if (this.#pending.size)
56
+ throw new Error("Editor recovery requires settled saves");
57
+ this.cancelChange();
58
+ this.cancelSave();
59
+ this.#failure = undefined;
60
+ this.#unverified = false;
61
+ }
31
62
  queue(snapshot, explicit = false) {
32
63
  this.#latest = snapshot;
33
64
  if (this.options.unownedExternal)
@@ -1,10 +1,15 @@
1
1
  import { EventDispatcher } from "./events";
2
+ export type ExtensionRegistrationOptions = {
3
+ fileIcon?: string;
4
+ };
2
5
  export type EditorViewPriority = "default" | "option" | "exclusive";
3
6
  export type EditorViewContribution = {
4
7
  id: string;
5
8
  viewType?: string;
6
9
  label: string;
7
10
  description?: string;
11
+ /** Registered API icon ID used for matching file rows. */
12
+ fileIcon?: string;
8
13
  filenamePatterns?: string[];
9
14
  priority?: EditorViewPriority;
10
15
  pluginId?: string;
@@ -23,8 +28,13 @@ export type EditorViewRegistryChange = {
23
28
  export declare class EditorViewRegistry extends EventDispatcher<{
24
29
  changed: [change: EditorViewRegistryChange];
25
30
  }> {
31
+ private readonly layers;
26
32
  private readonly views;
27
33
  register(contribution: EditorViewContribution): () => void;
34
+ /** Overlay runtime metadata on the same plugin's admitted manifest entry. */
35
+ overlay(contribution: EditorViewContribution): () => void;
36
+ private removeLayer;
37
+ private setVisible;
28
38
  upsert(contribution: EditorViewContribution): () => void;
29
39
  update(id: string, contribution: Partial<EditorViewContribution>): boolean;
30
40
  removeFilenamePatterns(id: string, patterns: readonly string[]): boolean;
@@ -19,6 +19,7 @@ function normalizeContribution(contribution) {
19
19
  id,
20
20
  viewType: normalizeString(contribution.viewType ?? id),
21
21
  label,
22
+ fileIcon: contribution.fileIcon?.trim() || undefined,
22
23
  filenamePatterns: uniqueStrings(contribution.filenamePatterns),
23
24
  priority: contribution.priority ?? "option",
24
25
  };
@@ -35,6 +36,7 @@ function mergeContribution(current, next) {
35
36
  };
36
37
  }
37
38
  export class EditorViewRegistry extends EventDispatcher {
39
+ layers = new Map();
38
40
  views = new Map();
39
41
  register(contribution) {
40
42
  const normalized = normalizeContribution(contribution);
@@ -42,12 +44,48 @@ export class EditorViewRegistry extends EventDispatcher {
42
44
  console.warn(`Editor view ${normalized.id} is already registered.`);
43
45
  return () => { };
44
46
  }
47
+ const owner = {};
48
+ this.layers.set(normalized.id, [{ owner, contribution: normalized }]);
45
49
  this.views.set(normalized.id, normalized);
46
50
  this.emit("changed", { id: normalized.id, action: "registered" });
47
51
  return () => {
48
- this.unregister(normalized.id);
52
+ this.removeLayer(normalized.id, owner);
49
53
  };
50
54
  }
55
+ /** Overlay runtime metadata on the same plugin's admitted manifest entry. */
56
+ overlay(contribution) {
57
+ const current = this.views.get(contribution.id);
58
+ if (!current)
59
+ return this.register(contribution);
60
+ const normalized = mergeContribution(current, contribution);
61
+ const owner = {};
62
+ this.layers.get(normalized.id).push({ owner, contribution: normalized });
63
+ this.views.set(normalized.id, normalized);
64
+ this.emit("changed", { id: normalized.id, action: "updated" });
65
+ return () => this.removeLayer(normalized.id, owner);
66
+ }
67
+ removeLayer(id, owner) {
68
+ const entries = this.layers.get(id);
69
+ const index = entries?.findIndex((entry) => entry.owner === owner) ?? -1;
70
+ if (!entries || index < 0)
71
+ return;
72
+ const visible = index === entries.length - 1;
73
+ entries.splice(index, 1);
74
+ if (!entries.length) {
75
+ this.unregister(id);
76
+ return;
77
+ }
78
+ if (visible) {
79
+ this.views.set(id, entries.at(-1).contribution);
80
+ this.emit("changed", { id, action: "updated" });
81
+ }
82
+ }
83
+ setVisible(id, contribution) {
84
+ this.views.set(id, contribution);
85
+ const top = this.layers.get(id)?.at(-1);
86
+ if (top)
87
+ top.contribution = contribution;
88
+ }
51
89
  upsert(contribution) {
52
90
  const normalized = normalizeContribution(contribution);
53
91
  const existing = this.views.get(normalized.id);
@@ -55,7 +93,7 @@ export class EditorViewRegistry extends EventDispatcher {
55
93
  return this.register(normalized);
56
94
  }
57
95
  const merged = mergeContribution(existing, normalized);
58
- this.views.set(merged.id, merged);
96
+ this.setVisible(merged.id, merged);
59
97
  this.emit("changed", { id: merged.id, action: "updated" });
60
98
  return () => { };
61
99
  }
@@ -70,7 +108,7 @@ export class EditorViewRegistry extends EventDispatcher {
70
108
  id: existing.id,
71
109
  label: contribution.label ?? existing.label,
72
110
  });
73
- this.views.set(existing.id, merged);
111
+ this.setVisible(existing.id, merged);
74
112
  this.emit("changed", { id: existing.id, action: "updated" });
75
113
  return true;
76
114
  }
@@ -86,14 +124,16 @@ export class EditorViewRegistry extends EventDispatcher {
86
124
  }
87
125
  if (filenamePatterns.length === 0 && existing.source === "compat") {
88
126
  this.views.delete(id);
127
+ this.layers.delete(id);
89
128
  this.emit("changed", { id, action: "unregistered" });
90
129
  return true;
91
130
  }
92
- this.views.set(id, { ...existing, filenamePatterns });
131
+ this.setVisible(id, { ...existing, filenamePatterns });
93
132
  this.emit("changed", { id, action: "updated" });
94
133
  return true;
95
134
  }
96
135
  unregister(id) {
136
+ this.layers.delete(id);
97
137
  const deleted = this.views.delete(id);
98
138
  if (deleted) {
99
139
  this.emit("changed", { id, action: "unregistered" });
@@ -1,10 +1,11 @@
1
- import { Annotation, Compartment, EditorState, Transaction, StateField, EditorSelection as CodeMirrorSelection, } from "@codemirror/state";
1
+ import { Annotation, Compartment, EditorState, StateEffect, Transaction, StateField, EditorSelection as CodeMirrorSelection, } from "@codemirror/state";
2
2
  import { EditorView } from "@codemirror/view";
3
3
  import { redo as redoCommand, undo as undoCommand } from "@codemirror/commands";
4
4
  import { EventDispatcher } from "./events";
5
5
  import { tick } from "svelte";
6
6
  import { EditorSaveContext, } from "./editor-save-context";
7
7
  import { resolveApplication } from "./application-compatibility";
8
+ import { FileTextPersistence } from "./file-text-persistence";
8
9
  export const editorViewField = StateField.define({
9
10
  create() {
10
11
  return {
@@ -50,32 +51,35 @@ export function createEditorState(editor, doc, extensions = [], selection) {
50
51
  return EditorState.create({
51
52
  doc,
52
53
  selection,
53
- extensions: [
54
- editorViewField.init(() => ({
55
- get app() {
56
- return editor.app;
57
- },
58
- editor,
59
- get file() {
60
- return editor.file;
61
- },
62
- })),
63
- editor.protectionExtensions(),
64
- EditorState.transactionFilter.of((transaction) => transaction.docChanged &&
65
- editor.editingProtected &&
66
- !transaction.annotation(syncAnnotation)
67
- ? []
68
- : transaction),
69
- EditorView.updateListener.of((v) => {
70
- if (v.docChanged &&
71
- !v.transactions.some((it) => it.annotation(syncAnnotation))) {
72
- editor.queueChange(v.state.doc.toString());
73
- }
74
- }),
75
- ...extensions,
76
- ],
54
+ extensions: editorStateExtensions(editor, extensions),
77
55
  });
78
56
  }
57
+ function editorStateExtensions(editor, extensions) {
58
+ return [
59
+ editorViewField.init(() => ({
60
+ get app() {
61
+ return editor.app;
62
+ },
63
+ editor,
64
+ get file() {
65
+ return editor.file;
66
+ },
67
+ })),
68
+ editor.protectionExtensions(),
69
+ EditorState.transactionFilter.of((transaction) => transaction.docChanged &&
70
+ editor.editingProtected &&
71
+ !transaction.annotation(syncAnnotation)
72
+ ? []
73
+ : transaction),
74
+ EditorView.updateListener.of((v) => {
75
+ if (v.docChanged &&
76
+ !v.transactions.some((it) => it.annotation(syncAnnotation))) {
77
+ editor.queueChange(v.state.doc.toString());
78
+ }
79
+ }),
80
+ ...extensions,
81
+ ];
82
+ }
79
83
  export function createEditor(editor, doc, ...extensions) {
80
84
  return new EditorView({
81
85
  doc,
@@ -169,7 +173,13 @@ export class Editor extends EventDispatcher {
169
173
  #retireDocument() {
170
174
  this.#document?.saves.retire();
171
175
  }
176
+ #serializeWrite(run) {
177
+ const result = this.#writeTail.then(run);
178
+ this.#writeTail = result.catch(() => undefined);
179
+ return result;
180
+ }
172
181
  #createDocument(application, file, persistence, writer) {
182
+ const initialText = this.data;
173
183
  const document = {
174
184
  application,
175
185
  file,
@@ -179,15 +189,15 @@ export class Editor extends EventDispatcher {
179
189
  };
180
190
  document.saves = new EditorSaveContext({
181
191
  unownedExternal: persistence === "external" && !writer,
182
- serialize: (run) => {
183
- const result = this.#writeTail.then(run);
184
- this.#writeTail = result.catch(() => undefined);
185
- return result;
186
- },
192
+ serialize: (run) => this.#serializeWrite(run),
187
193
  persist: async (snapshot) => {
194
+ if (document.unregister?.native)
195
+ await document.unregister.ready();
188
196
  if (file?.path !== document.path)
189
197
  throw new Error("Editor persistence target changed");
190
- if (persistence === "vault" && file)
198
+ if (document.text)
199
+ await document.text.persist(snapshot.content);
200
+ else if (persistence === "vault" && file)
191
201
  await application.vault.modify(file, snapshot.content);
192
202
  else if (persistence === "external" && writer)
193
203
  await writer({ application, file, content: snapshot.content });
@@ -209,10 +219,69 @@ export class Editor extends EventDispatcher {
209
219
  this.#documents.delete(document);
210
220
  },
211
221
  });
222
+ if (persistence === "vault" &&
223
+ file &&
224
+ application.vault.supportsConditionalTextWrites) {
225
+ document.text = new FileTextPersistence({
226
+ vault: application.vault,
227
+ file,
228
+ baseline: this.data,
229
+ conflicts: application.fileEditingSessions.textConflicts,
230
+ local: () => document.saves.latest?.content ?? this.data,
231
+ protect: () => ({
232
+ release: !this.destroyed && this.#document === document
233
+ ? this.#holdEditing()
234
+ : () => undefined,
235
+ saved: () => {
236
+ const snapshot = document.saves.latest;
237
+ if (!snapshot)
238
+ throw new Error("No retained editor snapshot");
239
+ document.saves.accept(snapshot);
240
+ },
241
+ reset: (contents) => {
242
+ if (!this.destroyed && this.#document === document) {
243
+ this.#replaceContent(contents);
244
+ this.onChange(contents);
245
+ }
246
+ document.saves.discard();
247
+ },
248
+ }),
249
+ settle: async () => {
250
+ await document.loading;
251
+ try {
252
+ await document.saves.flush(false);
253
+ }
254
+ catch (error) {
255
+ if (!document.text?.hasConflict)
256
+ throw error;
257
+ }
258
+ },
259
+ serialize: (run) => this.#serializeWrite(run),
260
+ });
261
+ }
212
262
  if (file) {
213
263
  if (!application.fileEditingSessions)
214
264
  throw new Error("App has no file editing-session registry");
215
265
  document.unregister = application.fileEditingSessions.register(file.path, {
266
+ native: {
267
+ hold: () => this.#holdEditing(),
268
+ savedText: () => document.text
269
+ ? document.text.baseline
270
+ : (document.saves.latest?.content ?? initialText),
271
+ refreshSavedText: (contents) => {
272
+ if (document.loading || document.saves.dirty || !document.text)
273
+ throw new Error("Native editor refresh requires clean conditional text authority");
274
+ document.text.acceptBaseline(contents);
275
+ if (!this.destroyed &&
276
+ this.#document === document &&
277
+ contents !== null) {
278
+ if (this.getValue() !== contents)
279
+ this.#replaceContent(contents);
280
+ this.data = contents;
281
+ this.onChange(contents);
282
+ }
283
+ },
284
+ },
216
285
  get dirty() {
217
286
  return !!document.loading || document.saves.dirty;
218
287
  },
@@ -294,7 +363,10 @@ export class Editor extends EventDispatcher {
294
363
  return Promise.reject(new Error("Editor is destroyed"));
295
364
  return this.#protectDocument(this.#currentDocument());
296
365
  }
297
- /** Register and protect a target, then publish its file and contents atomically. */
366
+ /**
367
+ * Register and protect a target, then publish its file and contents
368
+ * atomically.
369
+ */
298
370
  async loadDocument(file, read, publish) {
299
371
  if (this.destroyed)
300
372
  throw new Error("Editor is destroyed");
@@ -331,6 +403,14 @@ export class Editor extends EventDispatcher {
331
403
  this.#loading = load;
332
404
  let published = false;
333
405
  try {
406
+ if (document.unregister?.native) {
407
+ const admission = await Promise.race([
408
+ document.unregister.ready().then(() => ({ ready: true })),
409
+ cancellation,
410
+ ]);
411
+ if ("canceled" in admission || canceled)
412
+ return false;
413
+ }
334
414
  const drained = await Promise.race([
335
415
  this.flushChanges().then(() => ({ drained: true })),
336
416
  cancellation,
@@ -427,7 +507,9 @@ export class Editor extends EventDispatcher {
427
507
  }
428
508
  save = Object.assign(() => {
429
509
  this.#assertWritable();
430
- return this.#currentDocument().saves.queue(this.captureChange(this.getValue()), true);
510
+ const document = this.#currentDocument();
511
+ document.unregister?.changed();
512
+ return document.saves.queue(this.captureChange(this.getValue()), true);
431
513
  }, {
432
514
  flush: () => this.#document?.saves.flushSave(),
433
515
  cancel: () => {
@@ -444,7 +526,10 @@ export class Editor extends EventDispatcher {
444
526
  /** Queue the latest editor contents for persistence/change notification. */
445
527
  queueChange(data) {
446
528
  this.#assertWritable();
447
- this.#currentDocument().saves.queue(this.captureChange(data));
529
+ const document = this.#currentDocument();
530
+ document.unregister?.changed();
531
+ document.saves.queue(this.captureChange(data));
532
+ document.text?.updateLocal(data);
448
533
  }
449
534
  /** Drain every captured document, including failed or retired saves. */
450
535
  async flushChanges() {
@@ -534,6 +619,8 @@ export class Editor extends EventDispatcher {
534
619
  this.#refreshError = undefined;
535
620
  if (this.getValue() === contents) {
536
621
  this.data = contents;
622
+ if (!this.#document?.saves.dirty)
623
+ this.#document?.text?.acceptBaseline(contents);
537
624
  return;
538
625
  }
539
626
  if (this.getValue() !== this.data ||
@@ -575,10 +662,10 @@ export class Editor extends EventDispatcher {
575
662
  }
576
663
  span.setAttribute("editor.extension_count", editorExtensions.length);
577
664
  span.setAttribute("file.extension", ext ?? "");
578
- const content = this.getValue();
579
- const selection = this.view.state.selection;
580
665
  this.extensions = editorExtensions;
581
- this.view.setState(createEditorState(this, content, this.extensions, selection));
666
+ this.view.dispatch({
667
+ effects: StateEffect.reconfigure.of(editorStateExtensions(this, this.extensions)),
668
+ });
582
669
  return this;
583
670
  }, {
584
671
  attributes: { "file.extension": this.file?.extension ?? "" },
@@ -592,6 +679,8 @@ export class Editor extends EventDispatcher {
592
679
  throw new Error("Editor is destroyed");
593
680
  this.#documentGeneration++;
594
681
  this.#refreshError = undefined;
682
+ if (!this.#document?.saves.dirty)
683
+ this.#document?.text?.acceptBaseline(content);
595
684
  this.app.telemetry.measure("editor.set_value", (span) => {
596
685
  span.setAttribute("editor.content_length", content.length);
597
686
  span.setAttribute("file.extension", this.file?.extension ?? "");
@@ -609,6 +698,8 @@ export class Editor extends EventDispatcher {
609
698
  #replaceContent(content, options = {}) {
610
699
  this.#documentGeneration++;
611
700
  this.#refreshError = undefined;
701
+ if (!this.#document?.saves.dirty)
702
+ this.#document?.text?.acceptBaseline(content);
612
703
  this.data = content;
613
704
  const annotations = [syncAnnotation.of(true)];
614
705
  this.view.dispatch({
@@ -1,4 +1,5 @@
1
1
  import type { App } from "./context.svelte";
2
+ import type { TFile } from "./storage/fs";
2
3
  export type EmbedViewHandle = {
3
4
  destroy?: () => void | Promise<void>;
4
5
  };
@@ -7,9 +8,18 @@ export type EmbedView = (props: {
7
8
  containerEl: HTMLElement;
8
9
  state: Record<string, any>;
9
10
  }) => void | EmbedViewHandle;
11
+ export type MarkdownEmbedContext = {
12
+ app: App;
13
+ containerEl: HTMLElement;
14
+ file: TFile;
15
+ };
16
+ export type MarkdownEmbedder = (props: MarkdownEmbedContext) => false | EmbedViewHandle;
10
17
  export declare class EmbedRegistry {
18
+ #private;
11
19
  readonly embedByExtension: Record<string, EmbedView>;
12
20
  constructor();
21
+ registerMarkdownEmbedder(embedder: MarkdownEmbedder): () => void;
22
+ renderMarkdownEmbed(props: MarkdownEmbedContext): false | EmbedViewHandle;
13
23
  register(extension: string, view: EmbedView): () => void;
14
24
  get(extension: string): EmbedView | null;
15
25
  }
@@ -1,6 +1,21 @@
1
1
  export class EmbedRegistry {
2
2
  embedByExtension = {};
3
+ #markdownEmbedders = [];
3
4
  constructor() { }
5
+ registerMarkdownEmbedder(embedder) {
6
+ this.#markdownEmbedders = [...this.#markdownEmbedders, embedder];
7
+ return () => {
8
+ this.#markdownEmbedders = this.#markdownEmbedders.filter((candidate) => candidate !== embedder);
9
+ };
10
+ }
11
+ renderMarkdownEmbed(props) {
12
+ for (const embedder of this.#markdownEmbedders) {
13
+ const handle = embedder(props);
14
+ if (handle !== false)
15
+ return handle;
16
+ }
17
+ return false;
18
+ }
4
19
  register(extension, view) {
5
20
  const normalizedExtension = extension.toLowerCase();
6
21
  this.embedByExtension[normalizedExtension] = view;
@@ -0,0 +1,2 @@
1
+ /** Keep primary failures intact; cleanup may only re-report their object refs. */
2
+ export declare function unreportedCleanup(cleanup: readonly unknown[], primary: readonly unknown[]): unknown[];
@@ -0,0 +1,29 @@
1
+ /** Keep primary failures intact; cleanup may only re-report their object refs. */
2
+ export function unreportedCleanup(cleanup, primary) {
3
+ const reported = new Set();
4
+ const remember = (error) => {
5
+ if (error === null || typeof error !== "object" || reported.has(error))
6
+ return;
7
+ reported.add(error);
8
+ if (error instanceof AggregateError)
9
+ error.errors.forEach(remember);
10
+ };
11
+ primary.forEach(remember);
12
+ const retained = (error, ancestors) => {
13
+ if (reported.has(error))
14
+ return [];
15
+ if (!(error instanceof AggregateError) || ancestors.has(error))
16
+ return [error];
17
+ const next = new Set(ancestors).add(error);
18
+ const errors = error.errors.flatMap((child) => retained(child, next));
19
+ if (errors.length === error.errors.length &&
20
+ errors.every((child, index) => child === error.errors[index]))
21
+ return [error];
22
+ return errors.length
23
+ ? [
24
+ new AggregateError(errors, error.message, "cause" in error ? { cause: error.cause } : undefined),
25
+ ]
26
+ : [];
27
+ };
28
+ return cleanup.flatMap((error) => retained(error, new Set()));
29
+ }
@@ -0,0 +1,19 @@
1
+ import type { NativeEditingIssue, NativeRegisteredEditingSession } from "./native-editing-session";
2
+ export interface NativeEditingProblemIssue extends NativeEditingIssue {
3
+ readonly registration: string;
4
+ }
5
+ type Entry = {
6
+ path: string;
7
+ session: NativeRegisteredEditingSession;
8
+ retry(check: () => void): Promise<void>;
9
+ };
10
+ /** App-local failures remain visible even when native admission never succeeded. */
11
+ export declare class FileEditingNativeIssues {
12
+ #private;
13
+ get entries(): readonly NativeEditingProblemIssue[];
14
+ subscribe(listener: () => void): () => void;
15
+ /** @internal The editing registry owns registration and its guarded retry. */
16
+ register(path: string, session: NativeRegisteredEditingSession, retry: Entry["retry"]): () => void;
17
+ retry(captured: NativeEditingProblemIssue): Promise<void>;
18
+ }
19
+ export {};
@@ -0,0 +1,65 @@
1
+ /** App-local failures remain visible even when native admission never succeeded. */
2
+ export class FileEditingNativeIssues {
3
+ #entries = new Map();
4
+ #listeners = new Set();
5
+ get entries() {
6
+ return [...this.#entries].flatMap(([registration, entry]) => {
7
+ const issue = entry.session.issue;
8
+ return issue ? [Object.freeze({ ...issue, registration })] : [];
9
+ });
10
+ }
11
+ subscribe(listener) {
12
+ this.#listeners.add(listener);
13
+ listener();
14
+ return () => {
15
+ this.#listeners.delete(listener);
16
+ };
17
+ }
18
+ /** @internal The editing registry owns registration and its guarded retry. */
19
+ register(path, session, retry) {
20
+ const registration = crypto.randomUUID();
21
+ this.#entries.set(registration, { path, session, retry });
22
+ const stop = session.subscribe(() => this.#notify());
23
+ this.#notify();
24
+ return () => {
25
+ stop();
26
+ if (this.#entries.delete(registration))
27
+ this.#notify();
28
+ };
29
+ }
30
+ retry(captured) {
31
+ try {
32
+ const entry = this.#require(captured);
33
+ return entry.retry(() => {
34
+ this.#require(captured);
35
+ });
36
+ }
37
+ catch (error) {
38
+ return Promise.reject(error);
39
+ }
40
+ }
41
+ #require(captured) {
42
+ const entry = this.#entries.get(captured.registration);
43
+ const issue = entry?.session.issue;
44
+ if (!entry ||
45
+ entry.path !== captured.path ||
46
+ !issue ||
47
+ issue.id !== captured.id ||
48
+ issue.path !== captured.path ||
49
+ issue.phase !== captured.phase ||
50
+ captured.status !== "failed" ||
51
+ issue.status !== "failed")
52
+ throw new Error("Editing admission changed; refresh Problems");
53
+ return entry;
54
+ }
55
+ #notify() {
56
+ for (const listener of this.#listeners) {
57
+ try {
58
+ listener();
59
+ }
60
+ catch (error) {
61
+ console.error("Editing admission subscriber failed", error);
62
+ }
63
+ }
64
+ }
65
+ }