@type32/codemirror-rich-obsidian-editor 0.1.20 → 0.1.22

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/README.md CHANGED
@@ -73,228 +73,6 @@ Customize the editor fonts:
73
73
  }
74
74
  ```
75
75
 
76
- ## Components
77
-
78
- ### `Editor.client.vue`
79
-
80
- The main WYSIWYG Obsidian-Flavored Markdown editor component.
81
-
82
- **Props:**
83
- - `v-model`: Document content (string)
84
- - `internalLinkMap`: Array of internal link mappings for custom link rendering
85
- - `specialCodeBlockMap`: Array of custom code block component mappings
86
- - `bracketClosing`: Enable automatic bracket closing (default: true)
87
- - `foldGutter`: Enable code folding gutter (default: true)
88
- - `disabled`: Disable editing (default: false)
89
- - `searchOptions`: Search configuration object
90
-
91
- **Events:**
92
- - `@internal-link-click`: Emitted when an internal link is clicked
93
- - `@external-link-click`: Emitted when an external link is clicked
94
-
95
- ### `CodeEditor.client.vue`
96
-
97
- A styled code-only editor with syntax highlighting for multiple languages.
98
-
99
- **Props:**
100
- - `v-model`: Code content (string)
101
- - `language`: Programming language for syntax highlighting (e.g., 'javascript', 'typescript', 'json', 'yaml')
102
- - `lightTheme`: Custom CodeMirror theme extension for light mode (default: Catppuccin Latte)
103
- - `darkTheme`: Custom CodeMirror theme extension for dark mode (default: Catppuccin Mocha)
104
- - `colorMode`: Color mode ('dark' or 'light', default: 'dark')
105
- - `bracketClosing`: Enable automatic bracket closing (default: true)
106
- - `foldGutter`: Enable code folding gutter (default: true)
107
- - `disabled`: Disable editing (default: false)
108
-
109
- **Example Usage:**
110
-
111
- ```vue
112
- <template>
113
- <CodeEditor
114
- v-model="code"
115
- language="typescript"
116
- :color-mode="colorMode"
117
- :dark-theme="customDarkTheme"
118
- :light-theme="customLightTheme"
119
- />
120
- </template>
121
-
122
- <script setup lang="ts">
123
- import { ref } from 'vue'
124
- import { oneDark } from '@codemirror/theme-one-dark'
125
-
126
- const code = ref('console.log("Hello World")')
127
- const colorMode = ref('dark')
128
-
129
- // Optional: Use custom themes
130
- const customDarkTheme = oneDark
131
- const customLightTheme = undefined // Will use default Catppuccin Latte
132
- </script>
133
- ```
134
-
135
- ## Composables
136
-
137
- This module provides powerful composables for interacting with the editor programmatically. All composables are **reactive** and **null-safe** during initialization.
138
-
139
- ### `useEditorUtils(editor: Ref)`
140
-
141
- Provides utilities for document manipulation, AST operations, and search functionality.
142
-
143
- #### Reactive Properties
144
- - **`doc`**: Computed property that automatically updates when the editor content changes
145
- - **`view`**: Computed property for the CodeMirror EditorView instance
146
- - **`searchResults`**: Ref containing current search matches
147
- - **`currentMatchIndex`**: Ref for the currently selected search match
148
-
149
- #### Document Operations
150
- - **`getDoc()`**: Get current document content (snapshot)
151
- - **`setDoc(content: string)`**: Replace entire document
152
- - **`getSelection()`**: Get current selection
153
- - **`replaceSelection(text: string)`**: Replace selected text
154
- - **`dispatch(...specs: TransactionSpec[])`**: Dispatch editor transactions
155
-
156
- #### AST Operations
157
- - **`getDocAst()`**: Get parsed markdown AST
158
- - **`findNodesByType(tree: Tree, nodeTypeName: string)`**: Find nodes by type
159
- - **`getDocNodesByType(nodeTypeName: string)`**: Find nodes in current document
160
- - **`hasFrontmatter()`**: Check if document has frontmatter
161
-
162
- #### Search Operations
163
- - **`search(options: SearchOptions)`**: Search in document
164
- - **`findNext()`**, **`findPrevious()`**: Navigate search results
165
- - **`replaceCurrent(replacement: string)`**: Replace current match
166
- - **`replaceAll(replacement: string)`**: Replace all matches
167
-
168
- #### Example Usage
169
-
170
- ```vue
171
- <script setup lang="ts">
172
- import { ref, watch } from 'vue'
173
-
174
- const editor = ref()
175
- const { doc, frontmatter, getDoc, setDoc } = useEditorUtils(editor)
176
-
177
- // Reactive: automatically updates when editor content changes
178
- watch(doc, (newContent) => {
179
- console.log('Document changed:', newContent)
180
- })
181
-
182
- // Non-reactive: get current snapshot
183
- function saveDocument() {
184
- const content = getDoc()
185
- // Save content...
186
- }
187
- </script>
188
- ```
189
-
190
- ### `useEditorFrontmatter<T>(editor: Ref)`
191
-
192
- Provides utilities for managing YAML frontmatter with full reactivity and type safety.
193
-
194
- #### Reactive Properties
195
- - **`frontmatter`**: Computed property that automatically parses and updates when frontmatter changes
196
- - Returns `{ data?: T, error?: Error }`
197
-
198
- #### Methods
199
- - **`getFrontmatter()`**: Get current frontmatter (snapshot)
200
- - **`setFrontmatterProperties(properties: Partial<T>)`**: Replace all frontmatter
201
- - Returns `boolean` indicating success
202
- - Removes frontmatter entirely if properties is empty
203
- - **`updateFrontmatterProperties(properties: Partial<T>)`**: Merge with existing frontmatter
204
- - Returns `boolean` indicating success
205
- - Preserves existing properties
206
- - **`clearFrontmatter()`**: Completely remove frontmatter from document
207
- - Returns `boolean` indicating success
208
- - Removes YAML delimiters and trailing newlines
209
- - **`addFrontmatterProperty(key: string, value: any)`**: Add/update single property
210
- - Returns `boolean` indicating success
211
- - **`removeFrontmatterProperty(key: string)`**: Remove single property
212
- - Returns `boolean` indicating success
213
-
214
- #### Example Usage
215
-
216
- ```vue
217
- <script setup lang="ts">
218
- import { ref, watch } from 'vue'
219
-
220
- interface MyFrontmatter {
221
- title?: string
222
- tags?: string[]
223
- date?: string
224
- }
225
-
226
- const editor = ref()
227
- const {
228
- frontmatter,
229
- setFrontmatterProperties,
230
- updateFrontmatterProperties,
231
- clearFrontmatter
232
- } = useEditorFrontmatter<MyFrontmatter>(editor)
233
-
234
- // Reactive: automatically updates when frontmatter changes
235
- watch(frontmatter, (fm) => {
236
- if (fm.data) {
237
- console.log('Title:', fm.data.title)
238
- console.log('Tags:', fm.data.tags)
239
- }
240
- })
241
-
242
- // Replace all frontmatter
243
- function setMetadata() {
244
- setFrontmatterProperties({
245
- title: 'My Document',
246
- tags: ['vue', 'nuxt'],
247
- date: new Date().toISOString()
248
- })
249
- }
250
-
251
- // Update specific properties (preserves other properties)
252
- function addTag(tag: string) {
253
- const currentTags = frontmatter.value.data?.tags || []
254
- updateFrontmatterProperties({
255
- tags: [...currentTags, tag]
256
- })
257
- }
258
-
259
- // Remove all frontmatter
260
- function removeFrontmatter() {
261
- clearFrontmatter()
262
- }
263
- </script>
264
- ```
265
-
266
- ### Null Safety
267
-
268
- All composables are safe to use during Vue hydration when the editor ref may be `undefined` or `null`:
269
-
270
- ```typescript
271
- // Safe to call even if editor isn't ready yet
272
- const { doc, frontmatter, setDoc } = useEditorUtils(editor)
273
-
274
- // Reactive properties will be undefined until editor is initialized
275
- console.log(doc.value) // undefined initially
276
-
277
- // Methods return false or empty values if editor isn't ready
278
- const success = setDoc('New content') // Returns undefined if not ready
279
- ```
280
-
281
- ### Enabling Reactivity
282
-
283
- The reactive features are **automatically enabled** in both `Editor.client.vue` and `CodeEditor.client.vue` components. No additional setup is required.
284
-
285
- If you're creating a custom editor setup, include the reactivity extension:
286
-
287
- ```typescript
288
- import { createEditorReactivityExtension } from '@type32/codemirror-rich-obsidian-editor/composables/useEditorUtils'
289
-
290
- const editorInstance = shallowRef()
291
-
292
- const extensions = [
293
- // ... your other extensions
294
- createEditorReactivityExtension(editorInstance)
295
- ]
296
- ```
297
-
298
76
  ## Known Issues
299
77
  - Same as `segphault/codemirror-rich-markdoc`, the rendered block replacement code is not yet optimized, so it recomputes all of the replaced regions on every operation instead of only updating them as needed.
300
78
  - Progress is being made on this issue: we've optimized the Rich Text Plugin to update based on only the updated ranges instead of the entire document.
@@ -314,8 +92,7 @@ const extensions = [
314
92
  - We have a mapping prop that allows developers to add their own link-to-file implementations. (Specific to Vue/Nuxt)
315
93
  - ~~Support for code-block mermaid graph rendering & bases is lacking.~~
316
94
  - We have a mapping prop that allows developers to add their own custom codeblock widgets. (Specific to Vue/Nuxt)
317
- - ~~Light/Dark themes are not yet supported in code-block syntax highlighting.~~
318
- - The `CodeEditor.client.vue` component now supports customizable light/dark themes with Catppuccin as the default.
95
+ - Light/Dark themes are not yet supported in code-block syntax highlighting.
319
96
 
320
97
  ## Contributions
321
98
  - To anyone who wants to fork this, **make sure you preserve the original credits and references to the libraries that are used in this project. It means a lot to them and to us.**
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@type32/codemirror-rich-obsidian-editor",
3
3
  "configKey": "cmOfmEditor",
4
- "version": "0.1.20",
4
+ "version": "0.1.22",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -30,7 +30,6 @@ import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap }
30
30
  import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
31
31
  import { lintKeymap } from "@codemirror/lint";
32
32
  import { catppuccinLatte, catppuccinMocha } from "@catppuccin/codemirror";
33
- import { createEditorReactivityExtension } from "../composables/useEditorUtils";
34
33
  const doc = defineModel({ type: String });
35
34
  const props = defineProps({
36
35
  class: { type: String, required: false },
@@ -47,7 +46,6 @@ const props = defineProps({
47
46
  const emit = defineEmits([]);
48
47
  const extensions = shallowRef([]);
49
48
  const view = shallowRef();
50
- const editorInstance = shallowRef();
51
49
  const ast = ref([]);
52
50
  const languageCompartment = new Compartment();
53
51
  const themeCompartment = new Compartment();
@@ -128,9 +126,7 @@ onMounted(async () => {
128
126
  // Keys related to the linter system
129
127
  ...lintKeymap
130
128
  ]),
131
- EditorView.editable.of(!props.disabled),
132
- // Reactivity extension for composables
133
- createEditorReactivityExtension(editorInstance)
129
+ EditorView.editable.of(!props.disabled)
134
130
  ];
135
131
  });
136
132
  watch(
@@ -156,7 +152,6 @@ watch(
156
152
  );
157
153
  function handleReady(payload) {
158
154
  view.value = payload.view;
159
- editorInstance.value = payload;
160
155
  }
161
156
  function iterate() {
162
157
  ast.value = [];
@@ -21,7 +21,6 @@ import { internalLinkMapFacet } from "../editor/plugins/linkMappingConfig";
21
21
  import { specialCodeBlockMapFacet } from "../editor/plugins/specialCodeBlockMappingConfig";
22
22
  import { customBracketClosingConfig } from "../editor/plugins/customBracketClosingConfig";
23
23
  import { editorKeywordSearchPlugin, searchOptionsFacet } from "../editor/plugins/codemirror-editor-plugins/editorKeywordSearchPlugin";
24
- import { createEditorReactivityExtension } from "../composables/useEditorUtils";
25
24
  import { ref, shallowRef, computed, onMounted, onBeforeUnmount, unref, watch } from "vue";
26
25
  const doc = defineModel({ type: String });
27
26
  const props = defineProps({
@@ -37,7 +36,6 @@ const props = defineProps({
37
36
  const emit = defineEmits(["internal-link-click", "external-link-click"]);
38
37
  const extensions = shallowRef([]);
39
38
  const view = shallowRef();
40
- const editorInstance = shallowRef();
41
39
  const ast = ref([]);
42
40
  const internalLinkCompartment = new Compartment();
43
41
  const specialCodeBlockCompartment = new Compartment();
@@ -54,18 +52,11 @@ async function loadLanguage(info) {
54
52
  if (lang) {
55
53
  return await lang.load();
56
54
  }
57
- return null;
58
55
  }
59
56
  onMounted(() => {
60
57
  const wysiwygPlugin = wysiwyg({
61
58
  lezer: {
62
- codeLanguages: async (info) => {
63
- const result = await loadLanguage(info);
64
- if (result === null) {
65
- return null;
66
- }
67
- return result;
68
- }
59
+ codeLanguages: loadLanguage
69
60
  }
70
61
  });
71
62
  extensions.value = [
@@ -85,8 +76,7 @@ onMounted(() => {
85
76
  editorKeywordSearchPlugin,
86
77
  searchCompartment.of(searchOptionsFacet.of(props.searchOptions || { query: "" })),
87
78
  wysiwygPlugin,
88
- EditorView.editable.of(unref(!props.disabled)),
89
- createEditorReactivityExtension(editorInstance)
79
+ EditorView.editable.of(unref(!props.disabled))
90
80
  ];
91
81
  if (editorElement.value) {
92
82
  editorElement.value.addEventListener("internal-link-click", handleInternalLinkClick);
@@ -160,7 +150,6 @@ watch(
160
150
  );
161
151
  function handleReady(payload) {
162
152
  view.value = payload.view;
163
- editorInstance.value = payload;
164
153
  }
165
154
  function log(...args) {
166
155
  }
@@ -1,9 +1,5 @@
1
1
  import { type Ref } from 'vue';
2
2
  export declare function useEditorFrontmatter<T extends object = {}>(editor: Ref<any>): {
3
- frontmatter: import("vue").ComputedRef<{
4
- data?: T;
5
- error?: Error;
6
- }>;
7
3
  getFrontmatter: () => {
8
4
  data?: T;
9
5
  error?: Error;
@@ -1,39 +1,26 @@
1
- import { computed } from "vue";
2
1
  import { dump } from "js-yaml";
3
2
  import { useEditorUtils } from "./useEditorUtils.js";
4
3
  import { parseFrontmatter } from "../utils/frontmatter.js";
5
4
  export function useEditorFrontmatter(editor) {
6
5
  const editorUtils = useEditorUtils(editor);
7
- const frontmatter = computed(() => {
6
+ function getFrontmatter() {
8
7
  try {
9
- const doc = editorUtils.doc.value;
8
+ const doc = editorUtils.getDoc();
10
9
  if (!doc) {
11
- return {};
10
+ return { error: new Error("No document object found") };
12
11
  }
13
12
  return parseFrontmatter(doc);
14
13
  } catch (e) {
15
- console.error("Error parsing frontmatter:", e);
14
+ console.log(e);
16
15
  return { error: e };
17
16
  }
18
- });
19
- function getFrontmatter() {
20
- return frontmatter.value;
21
17
  }
22
18
  function updateFrontmatterProperties(properties) {
23
19
  try {
24
20
  const doc = editorUtils.getDoc();
25
- if (!doc) {
26
- console.warn("Editor not initialized or document is empty");
27
- return false;
28
- }
29
- const ast = editorUtils.getDocAst();
30
- if (!ast) {
31
- console.warn("Failed to parse document AST");
32
- return false;
33
- }
34
- const firstNode = ast.topNode.firstChild;
21
+ if (!doc) return false;
35
22
  let existingData = {};
36
- if (firstNode && (firstNode.name === "Frontmatter" || firstNode.name === "YAMLFrontMatter")) {
23
+ if (doc.startsWith("---\n") || doc.startsWith("---\r\n")) {
37
24
  const { data, error } = getFrontmatter();
38
25
  if (data && !error) {
39
26
  existingData = data;
@@ -42,17 +29,14 @@ export function useEditorFrontmatter(editor) {
42
29
  const newData = { ...existingData, ...properties };
43
30
  return setFrontmatterProperties(newData);
44
31
  } catch (e) {
45
- console.error("Error updating frontmatter properties:", e);
32
+ console.error("Error updating frontmatter:", e);
46
33
  return false;
47
34
  }
48
35
  }
49
36
  function setFrontmatterProperties(properties) {
50
37
  try {
51
38
  const doc = editorUtils.getDoc();
52
- if (doc === void 0) {
53
- console.warn("Editor not initialized or document is empty");
54
- return false;
55
- }
39
+ if (doc === void 0) return false;
56
40
  const newData = { ...properties };
57
41
  Object.keys(newData).forEach((key) => {
58
42
  if (newData[key] === void 0) {
@@ -64,11 +48,13 @@ export function useEditorFrontmatter(editor) {
64
48
  let frontmatterEnd = -1;
65
49
  if (doc.startsWith("---\n") || doc.startsWith("---\r\n")) {
66
50
  frontmatterStart = 0;
67
- const searchStart = doc.indexOf("\n", 3) + 1;
68
- const closingFenceIndex = doc.indexOf("\n---", searchStart);
51
+ const yamlStart = doc.indexOf("\n", 3) + 1;
52
+ const closingFenceIndex = doc.indexOf("\n---", yamlStart);
69
53
  if (closingFenceIndex !== -1) {
70
- frontmatterEnd = closingFenceIndex + 4;
71
- if (doc[frontmatterEnd] === "\n" || doc[frontmatterEnd] === "\r") {
54
+ const afterFence = closingFenceIndex + 4;
55
+ const nextChar = doc[afterFence];
56
+ if (nextChar === void 0 || nextChar === "\n" || nextChar === "\r") {
57
+ frontmatterEnd = afterFence;
72
58
  }
73
59
  }
74
60
  }
@@ -108,36 +94,32 @@ ${newYamlContent}
108
94
  }
109
95
  return true;
110
96
  } catch (e) {
111
- console.error("Error setting frontmatter properties:", e);
97
+ console.error("Error setting frontmatter:", e);
112
98
  return false;
113
99
  }
114
100
  }
115
101
  function clearFrontmatter() {
116
102
  try {
117
103
  const doc = editorUtils.getDoc();
118
- if (!doc) {
119
- console.warn("Editor not initialized or document is empty");
120
- return false;
121
- }
122
- let frontmatterStart = -1;
123
- let frontmatterEnd = -1;
124
- if (doc.startsWith("---\n") || doc.startsWith("---\r\n")) {
125
- frontmatterStart = 0;
126
- const searchStart = doc.indexOf("\n", 3) + 1;
127
- const closingFenceIndex = doc.indexOf("\n---", searchStart);
128
- if (closingFenceIndex !== -1) {
129
- frontmatterEnd = closingFenceIndex + 4;
130
- }
104
+ if (!doc) return false;
105
+ if (!doc.startsWith("---\n") && !doc.startsWith("---\r\n")) {
106
+ return true;
131
107
  }
132
- if (frontmatterStart !== -1 && frontmatterEnd !== -1) {
133
- let removeEnd = frontmatterEnd;
134
- if (doc[removeEnd] === "\n" || doc[removeEnd] === "\r") removeEnd++;
135
- if (doc[removeEnd] === "\n" || doc[removeEnd] === "\r") removeEnd++;
136
- editorUtils.dispatch({
137
- changes: { from: frontmatterStart, to: removeEnd, insert: "" }
138
- });
108
+ const yamlStart = doc.indexOf("\n", 3) + 1;
109
+ if (yamlStart === 0) return true;
110
+ const closingFenceIndex = doc.indexOf("\n---", yamlStart);
111
+ if (closingFenceIndex === -1) return true;
112
+ const afterFence = closingFenceIndex + 4;
113
+ const nextChar = doc[afterFence];
114
+ if (nextChar !== void 0 && nextChar !== "\n" && nextChar !== "\r") {
139
115
  return true;
140
116
  }
117
+ let removeEnd = afterFence;
118
+ if (doc[removeEnd] === "\n" || doc[removeEnd] === "\r") removeEnd++;
119
+ if (doc[removeEnd] === "\n" || doc[removeEnd] === "\r") removeEnd++;
120
+ editorUtils.dispatch({
121
+ changes: { from: 0, to: removeEnd, insert: "" }
122
+ });
141
123
  return true;
142
124
  } catch (e) {
143
125
  console.error("Error clearing frontmatter:", e);
@@ -151,10 +133,6 @@ ${newYamlContent}
151
133
  return updateFrontmatterProperties({ [key]: void 0 });
152
134
  }
153
135
  return {
154
- // Reactive properties
155
- frontmatter,
156
- // Reactive computed frontmatter data
157
- // Methods
158
136
  getFrontmatter,
159
137
  updateFrontmatterProperties,
160
138
  setFrontmatterProperties,
@@ -1,37 +1,29 @@
1
1
  import type { SyntaxNode, Tree } from '@lezer/common';
2
2
  import type { Ref } from 'vue';
3
- import type { TransactionSpec, Extension } from '@codemirror/state';
3
+ import type { TransactionSpec } from '@codemirror/state';
4
4
  import { parseMarkdownToAST } from '../utils/markdownParser.js';
5
5
  import type { SearchMatch, SearchOptions } from '../editor/types/editor-types.js';
6
- /**
7
- * Creates a CodeMirror extension that enables reactive composables.
8
- * This should be added to the editor's extensions array.
9
- */
10
- export declare function createEditorReactivityExtension(editorRef: Ref<any>): Extension;
11
6
  export declare function useEditorUtils(editor: Ref<any>): {
12
- doc: import("vue").ComputedRef<any>;
13
- view: import("vue").ComputedRef<any>;
14
- searchResults: Ref<{
15
- from: number;
16
- to: number;
17
- }[], SearchMatch[] | {
18
- from: number;
19
- to: number;
20
- }[]>;
21
- currentMatchIndex: Ref<number, number>;
22
- triggerReactivity: () => void;
23
7
  getDoc: () => string | undefined;
24
8
  setDoc: (content: string) => void;
25
9
  getSelection: () => any;
26
10
  replaceSelection: (text: string) => void;
27
11
  dispatch: (...specs: TransactionSpec[]) => void;
28
12
  parseMarkdownToAST: typeof parseMarkdownToAST;
29
- getDocAst: () => Tree | undefined;
13
+ getDocAst: () => Tree;
30
14
  findNodesByType: (tree: Tree, nodeTypeName: string) => SyntaxNode[];
31
15
  getDocNodesByType: (nodeTypeName: string) => SyntaxNode[];
32
16
  hasFrontmatter: () => boolean;
33
17
  search: (options: SearchOptions) => void;
34
18
  replaceAll: (replacement: string) => void;
19
+ searchResults: Ref<{
20
+ from: number;
21
+ to: number;
22
+ }[], SearchMatch[] | {
23
+ from: number;
24
+ to: number;
25
+ }[]>;
26
+ currentMatchIndex: Ref<number, number>;
35
27
  findNext: () => void;
36
28
  findPrevious: () => void;
37
29
  replaceCurrent: (replacement: string) => void;
@@ -1,94 +1,50 @@
1
1
  import { computed, ref, unref } from "vue";
2
2
  import { EditorView } from "@codemirror/view";
3
3
  import { parseMarkdownToAST } from "../utils/markdownParser.js";
4
- const reactivityCallbacks = /* @__PURE__ */ new WeakMap();
5
- export function createEditorReactivityExtension(editorRef) {
6
- return EditorView.updateListener.of((update) => {
7
- if (update.docChanged) {
8
- const callbacks = reactivityCallbacks.get(editorRef);
9
- if (callbacks) {
10
- callbacks.forEach((cb) => cb());
11
- }
12
- }
13
- });
14
- }
15
4
  export function useEditorUtils(editor) {
16
5
  const view = computed(() => {
17
6
  const instance = unref(editor);
18
7
  if (!instance) return;
19
8
  return instance.view ?? instance;
20
9
  });
21
- const docVersion = ref(0);
22
- if (!reactivityCallbacks.has(editor)) {
23
- reactivityCallbacks.set(editor, /* @__PURE__ */ new Set());
24
- }
25
- const callbacks = reactivityCallbacks.get(editor);
26
- const triggerReactivity = () => {
27
- docVersion.value++;
28
- };
29
- callbacks.add(triggerReactivity);
30
- const doc = computed(() => {
31
- docVersion.value;
32
- try {
33
- const editorView = unref(view);
34
- if (!editorView) return void 0;
35
- return editorView.state.doc.toString();
36
- } catch (e) {
37
- console.error("Error getting document:", e);
38
- return void 0;
39
- }
40
- });
41
10
  const searchResults = ref([]);
42
11
  const currentMatchIndex = ref(-1);
43
12
  const searchQuery = ref(null);
44
13
  function createSearchRegex(options) {
45
14
  return new RegExp(options.query, options.caseSensitive ? "g" : "gi");
46
15
  }
47
- function findAllMatches(doc2, options) {
16
+ function findAllMatches(doc, options) {
48
17
  const matches = [];
49
18
  const regex = createSearchRegex(options);
50
19
  let match;
51
- while ((match = regex.exec(doc2)) !== null) {
20
+ while ((match = regex.exec(doc)) !== null) {
52
21
  matches.push({ from: match.index, to: match.index + match[0].length });
53
22
  }
54
23
  return matches;
55
24
  }
56
25
  function getDoc() {
57
- return doc.value;
26
+ try {
27
+ return unref(view)?.state.doc.toString();
28
+ } catch (e) {
29
+ console.error(e);
30
+ }
58
31
  }
59
32
  function setDoc(content) {
60
- const editorView = unref(view);
61
- if (!editorView) {
62
- console.warn("Editor not initialized");
63
- return;
64
- }
65
- editorView.dispatch({
66
- changes: { from: 0, to: editorView.state.doc.length, insert: content }
33
+ unref(view)?.dispatch({
34
+ changes: { from: 0, to: unref(view).state.doc.length, insert: content }
67
35
  });
68
36
  }
69
37
  function getSelection() {
70
38
  return unref(view)?.state.selection.main;
71
39
  }
72
40
  function replaceSelection(text) {
73
- const editorView = unref(view);
74
- if (!editorView) {
75
- console.warn("Editor not initialized");
76
- return;
77
- }
78
- editorView.dispatch(editorView.state.replaceSelection(text));
41
+ unref(view)?.dispatch(unref(view).state.replaceSelection(text));
79
42
  }
80
43
  function dispatch(...specs) {
81
- const editorView = unref(view);
82
- if (!editorView) {
83
- console.warn("Editor not initialized");
84
- return;
85
- }
86
- editorView.dispatch(...specs);
44
+ unref(view)?.dispatch(...specs);
87
45
  }
88
46
  function getDocAst() {
89
- const docContent = getDoc();
90
- if (!docContent) return void 0;
91
- return parseMarkdownToAST(docContent);
47
+ return parseMarkdownToAST(getDoc() || "");
92
48
  }
93
49
  function findNodesByType(tree, nodeTypeName) {
94
50
  const nodes = [];
@@ -102,9 +58,7 @@ export function useEditorUtils(editor) {
102
58
  return nodes;
103
59
  }
104
60
  function getDocNodesByType(nodeTypeName) {
105
- const ast = getDocAst();
106
- if (!ast) return [];
107
- return findNodesByType(ast, nodeTypeName);
61
+ return findNodesByType(getDocAst(), nodeTypeName);
108
62
  }
109
63
  function hasFrontmatter() {
110
64
  const ast = getDocAst();
@@ -114,13 +68,13 @@ export function useEditorUtils(editor) {
114
68
  }
115
69
  function search(options) {
116
70
  searchQuery.value = options;
117
- const doc2 = getDoc();
118
- if (!doc2 || !options.query) {
71
+ const doc = getDoc();
72
+ if (!doc || !options.query) {
119
73
  searchResults.value = [];
120
74
  currentMatchIndex.value = -1;
121
75
  return;
122
76
  }
123
- searchResults.value = findAllMatches(doc2, options);
77
+ searchResults.value = findAllMatches(doc, options);
124
78
  currentMatchIndex.value = -1;
125
79
  }
126
80
  function selectAndScrollToMatch(match, verticalScrollStrategy = "start", verticalMargin) {
@@ -168,9 +122,9 @@ export function useEditorUtils(editor) {
168
122
  }
169
123
  function replaceAll(replacement) {
170
124
  if (!searchQuery.value || !searchQuery.value.query) return;
171
- const doc2 = getDoc();
172
- if (!doc2) return;
173
- const matches = findAllMatches(doc2, searchQuery.value);
125
+ const doc = getDoc();
126
+ if (!doc) return;
127
+ const matches = findAllMatches(doc, searchQuery.value);
174
128
  if (matches.length === 0) return;
175
129
  const changes = matches.map((m) => ({
176
130
  from: m.from,
@@ -189,36 +143,24 @@ export function useEditorUtils(editor) {
189
143
  });
190
144
  }
191
145
  return {
192
- // Reactive properties
193
- doc,
194
- // Reactive computed document content
195
- view,
196
- // Reactive computed editor view
197
- searchResults,
198
- currentMatchIndex,
199
- // Reactivity helpers
200
- triggerReactivity,
201
- // Manual trigger for reactivity
202
- // Document operations
203
146
  getDoc,
204
147
  setDoc,
205
148
  getSelection,
206
149
  replaceSelection,
207
150
  dispatch,
208
- // AST operations
209
151
  parseMarkdownToAST,
210
152
  // Re-exported from utils for convenience
211
153
  getDocAst,
212
154
  findNodesByType,
213
155
  getDocNodesByType,
214
156
  hasFrontmatter,
215
- // Search operations
216
157
  search,
217
158
  replaceAll,
159
+ searchResults,
160
+ currentMatchIndex,
218
161
  findNext,
219
162
  findPrevious,
220
163
  replaceCurrent,
221
- // Scroll operations
222
164
  scrollToNode
223
165
  };
224
166
  }
@@ -1,4 +1,8 @@
1
1
  import type { Frontmatter } from '../editor/types/editor-types.js';
2
+ /**
3
+ * Lightning-fast frontmatter parser using string operations instead of AST parsing.
4
+ * Optimized for performance - parses in microseconds instead of milliseconds.
5
+ */
2
6
  export declare function parseFrontmatter(markdownText: string): {
3
7
  data?: Frontmatter;
4
8
  error?: Error;
@@ -1,16 +1,25 @@
1
1
  import { load } from "js-yaml";
2
- import { parseMarkdownToAST } from "./markdownParser.js";
3
2
  export function parseFrontmatter(markdownText) {
4
3
  if (!markdownText) {
5
4
  return { error: new Error("No markdown text provided") };
6
5
  }
7
- const tree = parseMarkdownToAST(markdownText);
8
- const firstNode = tree.topNode.firstChild;
9
- if (!firstNode || firstNode.name !== "YAMLFrontMatter") {
6
+ if (!markdownText.startsWith("---\n") && !markdownText.startsWith("---\r\n")) {
10
7
  return { data: {} };
11
8
  }
12
- const contentNode = firstNode.getChild("YAMLContent");
13
- const yamlContent = contentNode ? markdownText.slice(contentNode.from, contentNode.to) : "";
9
+ const yamlStart = markdownText.indexOf("\n", 3) + 1;
10
+ if (yamlStart === 0) {
11
+ return { data: {} };
12
+ }
13
+ const closingFenceIndex = markdownText.indexOf("\n---", yamlStart);
14
+ if (closingFenceIndex === -1) {
15
+ return { data: {} };
16
+ }
17
+ const afterFence = closingFenceIndex + 4;
18
+ const nextChar = markdownText[afterFence];
19
+ if (nextChar !== void 0 && nextChar !== "\n" && nextChar !== "\r") {
20
+ return { data: {} };
21
+ }
22
+ const yamlContent = markdownText.slice(yamlStart, closingFenceIndex);
14
23
  try {
15
24
  const data = load(yamlContent);
16
25
  if (data === null || data === void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@type32/codemirror-rich-obsidian-editor",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "OFM Editor Component for Nuxt.",
5
5
  "repository": "https://github.com/Type-32/codemirror-rich-obsidian",
6
6
  "license": "MIT",