@stephen-netu/design-system 0.2.2 → 0.5.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 (229) hide show
  1. package/dist/d0-data-viz/BarChart.test.js +53 -0
  2. package/dist/d0-data-viz/CheckpointBar.test.js +83 -0
  3. package/dist/d0-data-viz/CheckpointPill.svelte +1 -1
  4. package/dist/d0-data-viz/CheckpointPill.test.js +78 -0
  5. package/dist/d0-data-viz/ConnectionHealth.test.js +70 -0
  6. package/dist/d0-data-viz/MetricCard.test.js +57 -0
  7. package/dist/d0-data-viz/SparkLine.test.js +47 -0
  8. package/dist/d0-data-viz/StorageMap.test.js +63 -0
  9. package/dist/d0-data-viz/Timeline.test.js +62 -0
  10. package/dist/d0-data-viz/WorldLineTimeline.test.js +99 -0
  11. package/dist/d0-data-viz/checkpoint-types.js +1 -1
  12. package/dist/d0-data-viz/storage-map-types.d.ts.map +1 -1
  13. package/dist/d0-data-viz/storage-map-types.js +6 -3
  14. package/dist/d0-data-viz/theme-integration.js +1 -1
  15. package/dist/demos/AmbientPrimitivesDemo.svelte +103 -0
  16. package/dist/demos/AmbientPrimitivesDemo.svelte.d.ts +5 -0
  17. package/dist/demos/AmbientPrimitivesDemo.svelte.d.ts.map +1 -0
  18. package/dist/demos/NodepadBlocksShowcase.svelte +2 -1
  19. package/dist/demos/NodepadBlocksShowcase.svelte.d.ts.map +1 -1
  20. package/dist/demos/index.d.ts +1 -0
  21. package/dist/demos/index.d.ts.map +1 -1
  22. package/dist/demos/index.js +1 -0
  23. package/dist/editor/CodeEditor.svelte +83 -26
  24. package/dist/editor/CodeEditor.svelte.d.ts +0 -1
  25. package/dist/editor/CodeEditor.svelte.d.ts.map +1 -1
  26. package/dist/editor/ExternalChangeDialog.svelte +117 -0
  27. package/dist/editor/ExternalChangeDialog.svelte.d.ts +13 -0
  28. package/dist/editor/ExternalChangeDialog.svelte.d.ts.map +1 -0
  29. package/dist/editor/MarkdownEditor.svelte +104 -617
  30. package/dist/editor/MarkdownEditor.svelte.d.ts +1 -1
  31. package/dist/editor/MarkdownEditor.svelte.d.ts.map +1 -1
  32. package/dist/editor/codemirror-config.d.ts +36 -0
  33. package/dist/editor/codemirror-config.d.ts.map +1 -0
  34. package/dist/editor/codemirror-config.js +87 -0
  35. package/dist/editor/components/CorrectionTooltip.svelte +180 -159
  36. package/dist/editor/components/CorrectionTooltip.svelte.d.ts +7 -23
  37. package/dist/editor/components/CorrectionTooltip.svelte.d.ts.map +1 -1
  38. package/dist/editor/extensions/autocomplete.d.ts +2 -2
  39. package/dist/editor/extensions/autocomplete.d.ts.map +1 -1
  40. package/dist/editor/extensions/autocomplete.test.js +121 -0
  41. package/dist/editor/extensions/spellcheck.d.ts +21 -3
  42. package/dist/editor/extensions/spellcheck.d.ts.map +1 -1
  43. package/dist/editor/extensions/spellcheck.js +51 -9
  44. package/dist/editor/extensions/spellcheck.test.js +168 -0
  45. package/dist/editor/extensions/types.d.ts +2 -1
  46. package/dist/editor/extensions/types.d.ts.map +1 -1
  47. package/dist/editor/index.d.ts +2 -1
  48. package/dist/editor/index.d.ts.map +1 -1
  49. package/dist/editor/index.js +1 -0
  50. package/dist/editor/markdown-preview.d.ts +9 -0
  51. package/dist/editor/markdown-preview.d.ts.map +1 -0
  52. package/dist/editor/markdown-preview.js +19 -0
  53. package/dist/editor/markdown-preview.test.js +38 -0
  54. package/dist/editor/useCodeMirror.d.ts +23 -0
  55. package/dist/editor/useCodeMirror.d.ts.map +1 -0
  56. package/dist/editor/useCodeMirror.js +40 -0
  57. package/dist/editor/useFilePersistence.d.ts +32 -0
  58. package/dist/editor/useFilePersistence.d.ts.map +1 -0
  59. package/dist/editor/useFilePersistence.js +117 -0
  60. package/dist/fabric/ai/ChatPanel.svelte +61 -62
  61. package/dist/fabric/ai/ChatPanel.svelte.d.ts.map +1 -1
  62. package/dist/fabric/ai/ResearchPanel.svelte +31 -24
  63. package/dist/fabric/data/FileTree.svelte +195 -0
  64. package/dist/fabric/data/FileTree.svelte.d.ts +19 -0
  65. package/dist/fabric/data/FileTree.svelte.d.ts.map +1 -0
  66. package/dist/fabric/data/FileTree.test.js +127 -0
  67. package/dist/fabric/data/index.d.ts +5 -9
  68. package/dist/fabric/data/index.d.ts.map +1 -1
  69. package/dist/fabric/data/index.js +4 -1
  70. package/dist/fabric/index.d.ts +1 -0
  71. package/dist/fabric/index.d.ts.map +1 -1
  72. package/dist/fabric/index.js +2 -0
  73. package/dist/fabric/input/KeyboardShortcuts.svelte +23 -21
  74. package/dist/fabric/layout/CollapsiblePanel.svelte +8 -10
  75. package/dist/fabric/navigation/ActivityRail.svelte +3 -5
  76. package/dist/fabric/navigation/ActivityRail.svelte.d.ts.map +1 -1
  77. package/dist/g0-generative/components/Text.svelte +38 -0
  78. package/dist/g0-generative/components/Text.svelte.d.ts +9 -0
  79. package/dist/g0-generative/components/Text.svelte.d.ts.map +1 -0
  80. package/dist/g0-generative/index.d.ts +1 -0
  81. package/dist/g0-generative/index.d.ts.map +1 -1
  82. package/dist/g0-generative/index.js +1 -0
  83. package/dist/g0-generative/library/ComponentLibrary.d.ts +4 -0
  84. package/dist/g0-generative/library/ComponentLibrary.d.ts.map +1 -1
  85. package/dist/g0-generative/library/ComponentLibrary.js +10 -0
  86. package/dist/g0-generative/library/ComponentLibrary.test.js +135 -0
  87. package/dist/g0-generative/renderer/OpenUIRenderer.svelte +16 -56
  88. package/dist/g0-generative/renderer/OpenUIRenderer.svelte.d.ts.map +1 -1
  89. package/dist/g0-generative/renderer/OpenUIRenderer.test.js +143 -0
  90. package/dist/g0-geo/SovereignMap.svelte +13 -13
  91. package/dist/g0-geo/SovereignMap.svelte.d.ts.map +1 -1
  92. package/dist/index.d.ts +0 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +3 -1
  95. package/dist/l0-layout/block-writer/BlockContent.svelte +23 -22
  96. package/dist/l0-layout/block-writer/BlockContent.svelte.d.ts.map +1 -1
  97. package/dist/l0-layout/block-writer/BlockContent.test.js +47 -0
  98. package/dist/l0-layout/block-writer/markdown.js +11 -11
  99. package/dist/l0-layout/block-writer/markdown.test.js +81 -0
  100. package/dist/l0-layout/card/StackedCard.svelte +87 -0
  101. package/dist/l0-layout/card/StackedCard.svelte.d.ts +9 -0
  102. package/dist/l0-layout/card/StackedCard.svelte.d.ts.map +1 -0
  103. package/dist/l0-layout/card/StackedCard.test.js +29 -0
  104. package/dist/l0-layout/index.d.ts +1 -0
  105. package/dist/l0-layout/index.d.ts.map +1 -1
  106. package/dist/l0-layout/index.js +1 -0
  107. package/dist/l0-layout/kanban/KanbanBoard.svelte +1 -1
  108. package/dist/l0-layout/kanban/KanbanColumn.svelte +1 -1
  109. package/dist/n1-flow-canvas/FlowCanvas.svelte +50 -6
  110. package/dist/n1-flow-canvas/FlowCanvas.svelte.d.ts.map +1 -1
  111. package/dist/n1-flow-canvas/index.d.ts +2 -0
  112. package/dist/n1-flow-canvas/index.d.ts.map +1 -1
  113. package/dist/n1-flow-canvas/index.js +1 -0
  114. package/dist/n1-flow-canvas/pipeline-adapter.d.ts +37 -0
  115. package/dist/n1-flow-canvas/pipeline-adapter.d.ts.map +1 -0
  116. package/dist/n1-flow-canvas/pipeline-adapter.js +160 -0
  117. package/dist/n2-manifold-disk/ManifoldDisk.svelte +62 -35
  118. package/dist/n2-manifold-disk/ManifoldDisk.svelte.d.ts.map +1 -1
  119. package/dist/n4-adaptive-manifold/AdaptiveManifold.svelte +23 -19
  120. package/dist/n4-adaptive-manifold/AdaptiveManifold.svelte.d.ts.map +1 -1
  121. package/dist/n4-adaptive-manifold/types.d.ts +3 -0
  122. package/dist/n4-adaptive-manifold/types.d.ts.map +1 -1
  123. package/dist/p0-primitives/ThemeProvider.svelte +28 -7
  124. package/dist/p0-primitives/ThemeProvider.svelte.d.ts +5 -0
  125. package/dist/p0-primitives/ThemeProvider.svelte.d.ts.map +1 -1
  126. package/dist/p0-primitives/canvas-theme.d.ts +21 -1
  127. package/dist/p0-primitives/canvas-theme.d.ts.map +1 -1
  128. package/dist/p0-primitives/canvas-theme.js +78 -23
  129. package/dist/p0-primitives/debug.d.ts +6 -0
  130. package/dist/p0-primitives/debug.d.ts.map +1 -0
  131. package/dist/p0-primitives/debug.js +21 -0
  132. package/dist/p0-primitives/theme.d.ts.map +1 -1
  133. package/dist/p0-primitives/theme.js +11 -31
  134. package/dist/p0-primitives/tokens/ambient.css +76 -0
  135. package/dist/p0-primitives/tokens/tokens.css +124 -29
  136. package/dist/p0-primitives/utils/index.d.ts +2 -0
  137. package/dist/p0-primitives/utils/index.d.ts.map +1 -1
  138. package/dist/p0-primitives/utils/index.js +1 -0
  139. package/dist/p0-primitives/utils/reveal.d.ts +15 -0
  140. package/dist/p0-primitives/utils/reveal.d.ts.map +1 -0
  141. package/dist/p0-primitives/utils/reveal.js +29 -0
  142. package/dist/p0-primitives/utils/reveal.test.js +95 -0
  143. package/dist/s0-state/SyncStatus.svelte +1 -1
  144. package/dist/s0-state/index.d.ts +2 -1
  145. package/dist/s0-state/index.d.ts.map +1 -1
  146. package/dist/s0-state/index.js +2 -0
  147. package/dist/s0-state/modal-stack-store.svelte.d.ts +3 -3
  148. package/dist/s0-state/modal-stack-store.svelte.js +2 -2
  149. package/dist/spatial/ForceSimulation.svelte.js +1 -1
  150. package/dist/spatial/ParticleAssembleMark.svelte +285 -0
  151. package/dist/spatial/ParticleAssembleMark.svelte.d.ts +24 -0
  152. package/dist/spatial/ParticleAssembleMark.svelte.d.ts.map +1 -0
  153. package/dist/spatial/ZoneTiler.test.js +90 -0
  154. package/dist/spatial/index.js +1 -0
  155. package/dist/spatial/particle-assemble-math.d.ts +22 -0
  156. package/dist/spatial/particle-assemble-math.d.ts.map +1 -0
  157. package/dist/spatial/particle-assemble-math.js +26 -0
  158. package/dist/spatial/particle-assemble-math.test.js +56 -0
  159. package/dist/t0-transport/substrate.svelte.d.ts.map +1 -1
  160. package/dist/t0-transport/substrate.svelte.js +10 -4
  161. package/dist/u0-primitives/LockIndicator.svelte +16 -14
  162. package/dist/u0-primitives/VerticalToolbar.svelte +24 -21
  163. package/dist/u0-primitives/accordion/Accordion.svelte +8 -7
  164. package/dist/u0-primitives/animated-icon/AnimatedIcon.svelte +3 -3
  165. package/dist/u0-primitives/animated-icon/AnimatedIcon.test.js +82 -0
  166. package/dist/u0-primitives/avatar/Avatar.svelte +14 -14
  167. package/dist/u0-primitives/avatar/Avatar.test.js +81 -0
  168. package/dist/u0-primitives/badge/Badge.svelte +6 -4
  169. package/dist/u0-primitives/badge/ValidityBadge.svelte +4 -27
  170. package/dist/u0-primitives/button/Button.svelte +15 -12
  171. package/dist/u0-primitives/card/Card.svelte +37 -15
  172. package/dist/u0-primitives/card/Card.test.js +86 -0
  173. package/dist/u0-primitives/command-palette/CommandPalette.svelte +48 -43
  174. package/dist/u0-primitives/command-palette/CommandPalette.test.js +202 -0
  175. package/dist/u0-primitives/dropdown/Dropdown.svelte +20 -25
  176. package/dist/u0-primitives/grant-badge/GrantStatusBadge.svelte +2 -2
  177. package/dist/u0-primitives/index.d.ts +6 -0
  178. package/dist/u0-primitives/index.d.ts.map +1 -1
  179. package/dist/u0-primitives/index.js +8 -0
  180. package/dist/u0-primitives/input/Input.svelte +77 -24
  181. package/dist/u0-primitives/input/Input.svelte.d.ts +2 -10
  182. package/dist/u0-primitives/input/Input.svelte.d.ts.map +1 -1
  183. package/dist/u0-primitives/input/Input.test.js +81 -1
  184. package/dist/u0-primitives/live-dot/LiveDot.svelte +56 -0
  185. package/dist/u0-primitives/live-dot/LiveDot.svelte.d.ts +11 -0
  186. package/dist/u0-primitives/live-dot/LiveDot.svelte.d.ts.map +1 -0
  187. package/dist/u0-primitives/live-dot/LiveDot.test.js +41 -0
  188. package/dist/u0-primitives/live-dot/index.d.ts +3 -0
  189. package/dist/u0-primitives/live-dot/index.d.ts.map +1 -0
  190. package/dist/u0-primitives/live-dot/index.js +1 -0
  191. package/dist/u0-primitives/modal/Modal.svelte +22 -19
  192. package/dist/u0-primitives/modal/Modal.test.js +1 -1
  193. package/dist/u0-primitives/spinner/Spinner.svelte +11 -79
  194. package/dist/u0-primitives/spinner/Spinner.svelte.d.ts.map +1 -1
  195. package/dist/u0-primitives/steps/Steps.svelte +18 -19
  196. package/dist/u0-primitives/surface-state/SurfaceState.svelte +7 -7
  197. package/dist/u0-primitives/tabs/Tabs.svelte +13 -11
  198. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte +94 -0
  199. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte.d.ts +16 -0
  200. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.svelte.d.ts.map +1 -0
  201. package/dist/u0-primitives/theme-swatch-group/ThemeSwatchGroup.test.js +48 -0
  202. package/dist/u0-primitives/theme-swatch-group/index.d.ts +3 -0
  203. package/dist/u0-primitives/theme-swatch-group/index.d.ts.map +1 -0
  204. package/dist/u0-primitives/theme-swatch-group/index.js +1 -0
  205. package/dist/u0-primitives/toggle/Toggle.svelte +10 -11
  206. package/dist/u0-primitives/tooltip/Tooltip.svelte +9 -9
  207. package/dist/u0-primitives/web-components/button-component.d.ts +5 -1
  208. package/dist/u0-primitives/web-components/button-component.d.ts.map +1 -1
  209. package/dist/u0-primitives/web-components/button-component.js +72 -33
  210. package/dist/u0-primitives/web-components/index.d.ts +3 -0
  211. package/dist/u0-primitives/web-components/index.d.ts.map +1 -0
  212. package/dist/u0-primitives/web-components/index.js +2 -0
  213. package/dist/u0-primitives/web-components/input-component.d.ts +5 -1
  214. package/dist/u0-primitives/web-components/input-component.d.ts.map +1 -1
  215. package/dist/u0-primitives/web-components/input-component.js +56 -39
  216. package/dist/u0-primitives/web-components/web-components.test.js +96 -0
  217. package/dist/u0-primitives/window-frame/WindowFrame.svelte +87 -0
  218. package/dist/u0-primitives/window-frame/WindowFrame.svelte.d.ts +10 -0
  219. package/dist/u0-primitives/window-frame/WindowFrame.svelte.d.ts.map +1 -0
  220. package/dist/u0-primitives/window-frame/WindowFrame.test.js +31 -0
  221. package/dist/u0-primitives/window-frame/index.d.ts +3 -0
  222. package/dist/u0-primitives/window-frame/index.d.ts.map +1 -0
  223. package/dist/u0-primitives/window-frame/index.js +1 -0
  224. package/dist/x0-enchanted-blocks/components/NodepadBlock.svelte +11 -2
  225. package/dist/x0-enchanted-blocks/components/NodepadBlock.svelte.d.ts.map +1 -1
  226. package/package.json +44 -28
  227. package/src/p0-primitives/tokens/ambient.css +76 -0
  228. package/src/p0-primitives/tokens/tokens.css +124 -29
  229. package/src/p0-primitives/tokens/tokens.json +42 -24
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { EditorState } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { autocomplete } from './autocomplete';
5
+ function createView(doc, config) {
6
+ return new EditorView({
7
+ state: EditorState.create({
8
+ doc,
9
+ extensions: [autocomplete(config)],
10
+ }),
11
+ parent: document.body,
12
+ });
13
+ }
14
+ describe('autocomplete', () => {
15
+ let view;
16
+ afterEach(() => {
17
+ if (view)
18
+ view.destroy();
19
+ });
20
+ const simpleSource = (context) => {
21
+ const word = context.state.doc.toString();
22
+ const options = [
23
+ { label: 'apple', type: 'keyword' },
24
+ { label: 'application', type: 'keyword' },
25
+ { label: 'apply', type: 'keyword' },
26
+ { label: 'banana', type: 'keyword' },
27
+ ];
28
+ return {
29
+ from: 0,
30
+ options: options.filter((o) => o.label.startsWith(word)),
31
+ };
32
+ };
33
+ it('exports a valid extension', () => {
34
+ const ext = autocomplete({ source: simpleSource });
35
+ expect(ext).toBeDefined();
36
+ });
37
+ it('accepts a CompletionSource function', () => {
38
+ const ext = autocomplete({ source: simpleSource });
39
+ view = new EditorView({
40
+ state: EditorState.create({
41
+ doc: '',
42
+ extensions: [ext],
43
+ }),
44
+ parent: document.body,
45
+ });
46
+ expect(view.state.doc.toString()).toBe('');
47
+ view.destroy();
48
+ });
49
+ it('respects defaultKeymap option', () => {
50
+ const ext = autocomplete({ source: simpleSource, defaultKeymap: false });
51
+ expect(ext).toBeDefined();
52
+ });
53
+ it('respects closeOnBlur option', () => {
54
+ const ext = autocomplete({ source: simpleSource, closeOnBlur: false });
55
+ expect(ext).toBeDefined();
56
+ });
57
+ it('uses defaultKeymap true by default', () => {
58
+ const ext = autocomplete({ source: simpleSource });
59
+ view = new EditorView({
60
+ state: EditorState.create({
61
+ doc: 'test',
62
+ extensions: [ext],
63
+ }),
64
+ parent: document.body,
65
+ });
66
+ expect(view).toBeDefined();
67
+ view.destroy();
68
+ });
69
+ it('uses closeOnBlur true by default', () => {
70
+ const ext = autocomplete({ source: simpleSource });
71
+ view = new EditorView({
72
+ state: EditorState.create({
73
+ doc: '',
74
+ extensions: [ext],
75
+ }),
76
+ parent: document.body,
77
+ });
78
+ expect(view).toBeDefined();
79
+ view.destroy();
80
+ });
81
+ it('wraps @codemirror/autocomplete autocompletion()', () => {
82
+ const ext = autocomplete({ source: simpleSource });
83
+ expect(ext).toBeTruthy();
84
+ });
85
+ it('passes override array with the provided source', () => {
86
+ const ext = autocomplete({ source: simpleSource });
87
+ view = new EditorView({
88
+ state: EditorState.create({
89
+ doc: '',
90
+ extensions: [ext],
91
+ }),
92
+ parent: document.body,
93
+ });
94
+ expect(view.state).toBeDefined();
95
+ view.destroy();
96
+ });
97
+ it('works with empty document', () => {
98
+ view = createView('', { source: simpleSource });
99
+ expect(view.state.doc.toString()).toBe('');
100
+ });
101
+ it('works with existing text', () => {
102
+ view = createView('hello world', { source: simpleSource });
103
+ expect(view.state.doc.toString()).toBe('hello world');
104
+ });
105
+ it('CompletionSource receives typed context', () => {
106
+ const capturingSource = (context) => ({
107
+ from: context.state.doc.length,
108
+ options: [],
109
+ });
110
+ const ext = autocomplete({ source: capturingSource });
111
+ expect(ext).toBeDefined();
112
+ });
113
+ it('multiple autocomplete instances do not conflict', () => {
114
+ const view1 = createView('abc', { source: simpleSource });
115
+ const view2 = createView('xyz', { source: simpleSource });
116
+ expect(view1.state.doc.toString()).toBe('abc');
117
+ expect(view2.state.doc.toString()).toBe('xyz');
118
+ view1.destroy();
119
+ view2.destroy();
120
+ });
121
+ });
@@ -1,7 +1,25 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import type { Extension } from '@codemirror/state';
1
3
  export interface SpellCheckDictionary {
2
- check(word: string): boolean;
3
- suggest(word: string): string[];
4
+ check(word: string): boolean | Promise<boolean>;
5
+ suggest(word: string): string[] | Promise<string[]>;
4
6
  addWord(word: string): void;
5
7
  }
6
- export declare function spellcheck(dictionary: SpellCheckDictionary): import("@codemirror/state").Extension;
8
+ export interface SpellCheckCorrection {
9
+ original: string;
10
+ suggestions: string[];
11
+ position: {
12
+ top: number;
13
+ left: number;
14
+ };
15
+ }
16
+ export interface SpellCheckConfig {
17
+ onCorrectionClick?: (correction: SpellCheckCorrection, view: EditorView, from: number, to: number) => void | Promise<void>;
18
+ }
19
+ export declare function extractWords(text: string, maxWords?: number): {
20
+ word: string;
21
+ from: number;
22
+ to: number;
23
+ }[];
24
+ export declare function spellcheck(dictionary: SpellCheckDictionary, config?: SpellCheckConfig): Extension[];
7
25
  //# sourceMappingURL=spellcheck.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spellcheck.d.ts","sourceRoot":"","sources":["../../../src/editor/extensions/spellcheck.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACjC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAmBD,wBAAgB,UAAU,CAAC,UAAU,EAAE,oBAAoB,yCA4B1D"}
1
+ {"version":3,"file":"spellcheck.d.ts","sourceRoot":"","sources":["../../../src/editor/extensions/spellcheck.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACjC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC7B,iBAAiB,CAAC,EAAE,CAChB,UAAU,EAAE,oBAAoB,EAChC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,KACT,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAqB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,CAetH;AAMD,wBAAgB,UAAU,CAAC,UAAU,EAAE,oBAAoB,EAAE,MAAM,GAAE,gBAAqB,GAAG,SAAS,EAAE,CAwEvG"}
@@ -1,8 +1,10 @@
1
1
  import { linter } from '@codemirror/lint';
2
- function extractWords(text) {
2
+ import { EditorView } from '@codemirror/view';
3
+ const MAX_WORDS_PER_LINT = 4096;
4
+ export function extractWords(text, maxWords = MAX_WORDS_PER_LINT) {
3
5
  const words = [];
4
6
  let i = 0;
5
- while (i < text.length) {
7
+ while (i < text.length && words.length < maxWords) {
6
8
  if (/[a-zA-Z]/.test(text[i])) {
7
9
  const start = i;
8
10
  while (i < text.length && /[a-zA-Z']/.test(text[i])) {
@@ -16,18 +18,24 @@ function extractWords(text) {
16
18
  }
17
19
  return words;
18
20
  }
19
- export function spellcheck(dictionary) {
20
- const lintSource = (view) => {
21
+ function wordAt(view, pos) {
22
+ return extractWords(view.state.doc.toString()).find(({ from, to }) => pos >= from && pos <= to) ?? null;
23
+ }
24
+ export function spellcheck(dictionary, config = {}) {
25
+ const lintSource = async (view) => {
21
26
  const diagnostics = [];
22
27
  const words = extractWords(view.state.doc.toString());
23
28
  for (const { word, from, to } of words) {
24
- if (word.length > 1 && !dictionary.check(word)) {
29
+ if (word.length > 1 && !(await dictionary.check(word))) {
30
+ const suggestions = (await dictionary.suggest(word))
31
+ .slice(0, 8)
32
+ .sort((a, b) => a.localeCompare(b));
25
33
  diagnostics.push({
26
34
  from,
27
35
  to,
28
36
  severity: 'warning',
29
37
  message: `"${word}"`,
30
- actions: dictionary.suggest(word).slice(0, 8).map((suggestion) => ({
38
+ actions: suggestions.map((suggestion) => ({
31
39
  name: suggestion,
32
40
  apply(v, from, to) {
33
41
  v.dispatch({ changes: { from, to, insert: suggestion } });
@@ -38,7 +46,41 @@ export function spellcheck(dictionary) {
38
46
  }
39
47
  return diagnostics;
40
48
  };
41
- return linter(lintSource, {
42
- delay: 300,
43
- });
49
+ const clickHandler = config.onCorrectionClick
50
+ ? EditorView.domEventHandlers({
51
+ click: (event, view) => {
52
+ const target = event.target;
53
+ if (target?.closest('button, a, input, textarea, select'))
54
+ return false;
55
+ const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
56
+ if (pos === null)
57
+ return false;
58
+ const match = wordAt(view, pos);
59
+ if (!match || match.word.length <= 1)
60
+ return false;
61
+ void (async () => {
62
+ if (await dictionary.check(match.word))
63
+ return;
64
+ const coords = view.coordsAtPos(match.from);
65
+ if (!coords)
66
+ return;
67
+ const suggestions = (await dictionary.suggest(match.word))
68
+ .slice(0, 8)
69
+ .sort((a, b) => a.localeCompare(b));
70
+ await config.onCorrectionClick?.({
71
+ original: match.word,
72
+ suggestions,
73
+ position: { top: coords.top, left: coords.left },
74
+ }, view, match.from, match.to);
75
+ })();
76
+ return true;
77
+ },
78
+ })
79
+ : null;
80
+ return [
81
+ linter(lintSource, {
82
+ delay: 300,
83
+ }),
84
+ ...(clickHandler ? [clickHandler] : []),
85
+ ];
44
86
  }
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+ import { EditorState } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { spellcheck, extractWords } from './spellcheck';
5
+ function createMockDictionary(knownWords = []) {
6
+ const words = new Set(knownWords.map((w) => w.toLowerCase()));
7
+ const personalWords = [];
8
+ return {
9
+ check: (word) => words.has(word.toLowerCase()) || personalWords.includes(word.toLowerCase()),
10
+ suggest: (word) => {
11
+ const lower = word.toLowerCase();
12
+ return knownWords
13
+ .filter((w) => w.toLowerCase() !== lower && w.toLowerCase().startsWith(lower.charAt(0)))
14
+ .slice(0, 5);
15
+ },
16
+ addWord: (word) => {
17
+ personalWords.push(word.toLowerCase());
18
+ },
19
+ };
20
+ }
21
+ function createView(doc, dictionary) {
22
+ return new EditorView({
23
+ state: EditorState.create({
24
+ doc,
25
+ extensions: [spellcheck(dictionary)],
26
+ }),
27
+ parent: document.body,
28
+ });
29
+ }
30
+ describe('extractWords', () => {
31
+ it('extracts a single word', () => {
32
+ const result = extractWords('hello');
33
+ expect(result).toEqual([{ word: 'hello', from: 0, to: 5 }]);
34
+ });
35
+ it('extracts multiple words separated by spaces', () => {
36
+ const result = extractWords('hello world');
37
+ expect(result).toEqual([
38
+ { word: 'hello', from: 0, to: 5 },
39
+ { word: 'world', from: 6, to: 11 },
40
+ ]);
41
+ });
42
+ it('skips non-alpha characters', () => {
43
+ const result = extractWords('hello 123 world');
44
+ expect(result).toEqual([
45
+ { word: 'hello', from: 0, to: 5 },
46
+ { word: 'world', from: 10, to: 15 },
47
+ ]);
48
+ });
49
+ it('handles empty string', () => {
50
+ expect(extractWords('')).toEqual([]);
51
+ });
52
+ it('handles string with no alpha characters', () => {
53
+ expect(extractWords('123 456 !@#')).toEqual([]);
54
+ });
55
+ it('includes apostrophes in words', () => {
56
+ const result = extractWords("don't");
57
+ expect(result).toEqual([{ word: "don't", from: 0, to: 5 }]);
58
+ });
59
+ it('respects maxWords limit', () => {
60
+ const text = 'a b c d e f g h i j';
61
+ const result = extractWords(text, 3);
62
+ expect(result.length).toBe(3);
63
+ });
64
+ it('tracks correct from/to positions', () => {
65
+ const result = extractWords(' hello world ');
66
+ expect(result).toEqual([
67
+ { word: 'hello', from: 2, to: 7 },
68
+ { word: 'world', from: 9, to: 14 },
69
+ ]);
70
+ });
71
+ it('handles single character words', () => {
72
+ const result = extractWords('a b c');
73
+ expect(result).toEqual([
74
+ { word: 'a', from: 0, to: 1 },
75
+ { word: 'b', from: 2, to: 3 },
76
+ { word: 'c', from: 4, to: 5 },
77
+ ]);
78
+ });
79
+ it('handles mixed case', () => {
80
+ const result = extractWords('Hello WORLD');
81
+ expect(result).toEqual([
82
+ { word: 'Hello', from: 0, to: 5 },
83
+ { word: 'WORLD', from: 6, to: 11 },
84
+ ]);
85
+ });
86
+ });
87
+ describe('spellcheck', () => {
88
+ let view;
89
+ afterEach(() => {
90
+ if (view)
91
+ view.destroy();
92
+ });
93
+ it('returns an array of extensions', () => {
94
+ const dict = createMockDictionary(['hello']);
95
+ const exts = spellcheck(dict);
96
+ expect(Array.isArray(exts)).toBe(true);
97
+ expect(exts.length).toBeGreaterThan(0);
98
+ });
99
+ it('returns a linter extension', () => {
100
+ const dict = createMockDictionary(['hello']);
101
+ const exts = spellcheck(dict);
102
+ expect(exts.length).toBe(1);
103
+ });
104
+ it('includes click handler when onCorrectionClick is provided', () => {
105
+ const dict = createMockDictionary(['hello']);
106
+ const exts = spellcheck(dict, { onCorrectionClick: vi.fn() });
107
+ expect(exts.length).toBe(2);
108
+ });
109
+ it('works with empty document', () => {
110
+ const dict = createMockDictionary(['hello']);
111
+ view = createView('', dict);
112
+ expect(view.state.doc.toString()).toBe('');
113
+ });
114
+ it('works with known words only', () => {
115
+ const dict = createMockDictionary(['hello', 'world']);
116
+ view = createView('hello world', dict);
117
+ expect(view.state.doc.toString()).toBe('hello world');
118
+ });
119
+ it('works with unknown words', () => {
120
+ const dict = createMockDictionary(['hello']);
121
+ view = createView('hello xyzzy', dict);
122
+ expect(view.state.doc.toString()).toBe('hello xyzzy');
123
+ });
124
+ it('accepts a SpellCheckDictionary interface', () => {
125
+ const dict = {
126
+ check: () => true,
127
+ suggest: () => [],
128
+ addWord: () => { },
129
+ };
130
+ const exts = spellcheck(dict);
131
+ expect(exts.length).toBeGreaterThan(0);
132
+ });
133
+ it('accepts async check and suggest', () => {
134
+ const dict = {
135
+ check: async (word) => word === 'hello',
136
+ suggest: async (word) => ['help', 'hell'],
137
+ addWord: () => { },
138
+ };
139
+ const exts = spellcheck(dict);
140
+ expect(exts.length).toBeGreaterThan(0);
141
+ });
142
+ it('dictionary.addWord adds to personal dictionary', () => {
143
+ const dict = createMockDictionary(['hello']);
144
+ dict.addWord('xyzzy');
145
+ expect(dict.check('xyzzy')).toBe(true);
146
+ });
147
+ it('dictionary.suggest returns results', () => {
148
+ const dict = createMockDictionary(['help', 'heap', 'heel']);
149
+ const suggestions = dict.suggest('hel');
150
+ expect(suggestions.length).toBeGreaterThan(0);
151
+ expect(suggestions.every((s) => s.startsWith('h'))).toBe(true);
152
+ });
153
+ it('spellcheck with no config defaults to no click handler', () => {
154
+ const dict = createMockDictionary(['test']);
155
+ const exts = spellcheck(dict, {});
156
+ expect(exts.length).toBe(1);
157
+ });
158
+ it('handles text with punctuation', () => {
159
+ const dict = createMockDictionary(['hello']);
160
+ view = createView('hello, world!', dict);
161
+ expect(view.state.doc.toString()).toBe('hello, world!');
162
+ });
163
+ it('handles multiline text', () => {
164
+ const dict = createMockDictionary(['hello', 'world']);
165
+ view = createView('hello\nworld', dict);
166
+ expect(view.state.doc.toString()).toBe('hello\nworld');
167
+ });
168
+ });
@@ -1,3 +1,4 @@
1
+ export { type Completion, type CompletionSource } from './autocomplete';
1
2
  export { type ExpansionDictionary } from './expansion';
2
- export { type SpellCheckDictionary } from './spellcheck';
3
+ export { type SpellCheckCorrection, type SpellCheckConfig, type SpellCheckDictionary } from './spellcheck';
3
4
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/editor/extensions/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/editor/extensions/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
@@ -1,9 +1,10 @@
1
1
  export { default as MarkdownEditor } from './MarkdownEditor.svelte';
2
2
  export { default as CodeEditor } from './CodeEditor.svelte';
3
+ export { default as CorrectionTooltip } from './components/CorrectionTooltip.svelte';
3
4
  export { autoFormat } from './extensions/autoformat';
4
5
  export { expansion } from './extensions/expansion';
5
6
  export { autocomplete } from './extensions/autocomplete';
6
7
  export { spellcheck } from './extensions/spellcheck';
7
8
  export { type ExpansionDictionary } from './extensions/expansion';
8
- export type { SpellCheckDictionary } from './extensions/spellcheck';
9
+ export type { SpellCheckCorrection, SpellCheckConfig, SpellCheckDictionary } from './extensions/spellcheck';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export { default as MarkdownEditor } from './MarkdownEditor.svelte';
2
2
  export { default as CodeEditor } from './CodeEditor.svelte';
3
+ export { default as CorrectionTooltip } from './components/CorrectionTooltip.svelte';
3
4
  export { autoFormat } from './extensions/autoformat';
4
5
  export { expansion } from './extensions/expansion';
5
6
  export { autocomplete } from './extensions/autocomplete';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Minimal inline markdown → HTML conversion for the editor's preview pane.
3
+ *
4
+ * Intentionally lightweight (headings, bold/italic, inline code, list items,
5
+ * line breaks) — not a full CommonMark renderer. Extracted from
6
+ * MarkdownEditor.svelte so it can be unit-tested in isolation.
7
+ */
8
+ export declare function markdownToHtml(md: string): string;
9
+ //# sourceMappingURL=markdown-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-preview.d.ts","sourceRoot":"","sources":["../../src/editor/markdown-preview.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAWjD"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Minimal inline markdown → HTML conversion for the editor's preview pane.
3
+ *
4
+ * Intentionally lightweight (headings, bold/italic, inline code, list items,
5
+ * line breaks) — not a full CommonMark renderer. Extracted from
6
+ * MarkdownEditor.svelte so it can be unit-tested in isolation.
7
+ */
8
+ export function markdownToHtml(md) {
9
+ return md
10
+ .replace(/^### (.*$)/gim, '<h3>$1</h3>')
11
+ .replace(/^## (.*$)/gim, '<h2>$1</h2>')
12
+ .replace(/^# (.*$)/gim, '<h1>$1</h1>')
13
+ .replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
14
+ .replace(/\*(.*?)\*/gim, '<em>$1</em>')
15
+ .replace(/`([^`]+)`/gim, '<code>$1</code>')
16
+ .replace(/^- (.*$)/gim, '<li>$1</li>')
17
+ .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
18
+ .replace(/\n/gim, '<br>');
19
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { markdownToHtml } from './markdown-preview';
3
+ describe('editor/markdownToHtml', () => {
4
+ it('returns an empty string for empty input', () => {
5
+ expect(markdownToHtml('')).toBe('');
6
+ });
7
+ it('converts h1/h2/h3 headings', () => {
8
+ expect(markdownToHtml('# Title')).toBe('<h1>Title</h1>');
9
+ expect(markdownToHtml('## Title')).toBe('<h2>Title</h2>');
10
+ expect(markdownToHtml('### Title')).toBe('<h3>Title</h3>');
11
+ });
12
+ it('converts bold and italic', () => {
13
+ expect(markdownToHtml('**bold**')).toBe('<strong>bold</strong>');
14
+ expect(markdownToHtml('*italic*')).toBe('<em>italic</em>');
15
+ });
16
+ it('converts inline code', () => {
17
+ expect(markdownToHtml('`code`')).toBe('<code>code</code>');
18
+ });
19
+ it('wraps a run of list items in a <ul>', () => {
20
+ const html = markdownToHtml('- a\n- b');
21
+ expect(html).toContain('<ul>');
22
+ expect(html).toContain('<li>a</li>');
23
+ expect(html).toContain('<li>b</li>');
24
+ });
25
+ it('converts newlines to <br>', () => {
26
+ expect(markdownToHtml('line1\nline2')).toBe('line1<br>line2');
27
+ });
28
+ it('leaves plain text untouched', () => {
29
+ expect(markdownToHtml('just text')).toBe('just text');
30
+ });
31
+ it('handles mixed content', () => {
32
+ const html = markdownToHtml('# Heading\nSome **bold** and `code`.');
33
+ expect(html).toContain('<h1>Heading</h1>');
34
+ expect(html).toContain('<strong>bold</strong>');
35
+ expect(html).toContain('<code>code</code>');
36
+ expect(html).toContain('<br>');
37
+ });
38
+ });
@@ -0,0 +1,23 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import type { CompletionSource } from './extensions/autocomplete';
3
+ import type { SpellCheckCorrection, SpellCheckDictionary } from './extensions/spellcheck';
4
+ export interface UseCodeMirrorOptions {
5
+ getContainer: () => HTMLDivElement;
6
+ getInitialContent: () => string;
7
+ completionSource: () => CompletionSource | undefined;
8
+ spellCheckDictionary: () => SpellCheckDictionary | undefined;
9
+ onCorrectionClick: (correction: SpellCheckCorrection, view: EditorView, from: number, to: number) => void;
10
+ onSave: () => void;
11
+ onFind: () => void;
12
+ onFindReplace: () => void;
13
+ onDocChanged: (content: string) => void;
14
+ onEditorReady?: (view: EditorView) => void;
15
+ onEditorDestroy?: () => void;
16
+ }
17
+ export interface UseCodeMirrorApi {
18
+ getView: () => EditorView | null;
19
+ syncContent: (content: string) => void;
20
+ focus: () => void;
21
+ }
22
+ export declare function useCodeMirror(options: UseCodeMirrorOptions): UseCodeMirrorApi;
23
+ //# sourceMappingURL=useCodeMirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCodeMirror.d.ts","sourceRoot":"","sources":["../../src/editor/useCodeMirror.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE1F,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,cAAc,CAAC;IACnC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IACrD,oBAAoB,EAAE,MAAM,oBAAoB,GAAG,SAAS,CAAC;IAC7D,iBAAiB,EAAE,CAAC,UAAU,EAAE,oBAAoB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1G,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAwC7E"}
@@ -0,0 +1,40 @@
1
+ import { onDestroy, onMount } from 'svelte';
2
+ import { EditorState } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { buildExtensions } from './codemirror-config';
5
+ export function useCodeMirror(options) {
6
+ let view = null;
7
+ function destroyView() {
8
+ view?.destroy();
9
+ view = null;
10
+ options.onEditorDestroy?.();
11
+ }
12
+ onMount(() => {
13
+ const extensions = buildExtensions({
14
+ completionSource: options.completionSource(),
15
+ spellCheckDictionary: options.spellCheckDictionary(),
16
+ onCorrectionClick: options.onCorrectionClick,
17
+ onSave: options.onSave,
18
+ onFind: options.onFind,
19
+ onFindReplace: options.onFindReplace,
20
+ onDocChanged: options.onDocChanged
21
+ });
22
+ view = new EditorView({
23
+ state: EditorState.create({ doc: options.getInitialContent(), extensions }),
24
+ parent: options.getContainer()
25
+ });
26
+ options.onEditorReady?.(view);
27
+ });
28
+ onDestroy(destroyView);
29
+ return {
30
+ getView: () => view,
31
+ syncContent(content) {
32
+ if (!view || content === view.state.doc.toString())
33
+ return;
34
+ view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } });
35
+ },
36
+ focus() {
37
+ view?.focus();
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,32 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ export interface UseFilePersistenceOptions {
3
+ getFilePath: () => string | null;
4
+ getContent: () => string;
5
+ getIsDirty: () => boolean;
6
+ getIsSaving: () => boolean;
7
+ getIsCheckingExternalChanges: () => boolean;
8
+ getShowExternalChangeDialog: () => boolean;
9
+ getLastKnownMtimeMs: () => number | null;
10
+ getEditorView: () => EditorView | null;
11
+ setContent: (content: string) => void;
12
+ setIsDirty: (dirty: boolean) => void;
13
+ setIsSaving: (saving: boolean) => void;
14
+ setSaveError: (error: string | null) => void;
15
+ setShowExternalChangeDialog: (show: boolean) => void;
16
+ setIsCheckingExternalChanges: (checking: boolean) => void;
17
+ setLastKnownMtimeMs: (mtime: number | null) => void;
18
+ onChange?: (content: string) => void;
19
+ onSave?: () => void;
20
+ onExternalChange?: (content: string) => void;
21
+ }
22
+ export interface UseFilePersistenceApi {
23
+ persistenceAvailable: boolean;
24
+ startExternalChangeMonitoring: () => () => void;
25
+ stopExternalChangeMonitoring: () => void;
26
+ checkExternalChanges: () => Promise<void>;
27
+ saveContent: () => Promise<void>;
28
+ reloadFromDisk: () => Promise<void>;
29
+ keepCurrent: () => void;
30
+ }
31
+ export declare function useFilePersistence(options: UseFilePersistenceOptions): UseFilePersistenceApi;
32
+ //# sourceMappingURL=useFilePersistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFilePersistence.d.ts","sourceRoot":"","sources":["../../src/editor/useFilePersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,4BAA4B,EAAE,MAAM,OAAO,CAAC;IAC5C,2BAA2B,EAAE,MAAM,OAAO,CAAC;IAC3C,mBAAmB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACzC,aAAa,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC;IACvC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,2BAA2B,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,4BAA4B,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1D,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,6BAA6B,EAAE,MAAM,MAAM,IAAI,CAAC;IAChD,4BAA4B,EAAE,MAAM,IAAI,CAAC;IACzC,oBAAoB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,qBAAqB,CAmH5F"}