@type32/codemirror-rich-obsidian-editor 0.0.9 → 0.0.11
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 +1 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Editor.client.vue +9 -1
- package/dist/runtime/components/Editor.client.vue.d.ts +6 -6
- package/dist/runtime/composables/useDocumentUtils.d.ts +9 -0
- package/dist/runtime/composables/useDocumentUtils.js +37 -0
- package/dist/runtime/composables/useEditorUtils.js +9 -1
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorLinkClickPlugin.js +23 -7
- package/dist/runtime/editor/types/editor-types.d.ts +18 -1
- package/dist/runtime/editor/wysiwyg.js +4 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- https://github.com/ebullient/markdown-it-obsidian-callouts, for his awesome markdown-to-html callouts markdown-it plugin
|
|
9
9
|
- https://github.com/mgmeyers/obsidian-indentation-guides, for indentation guides
|
|
10
10
|
- Markdown-It
|
|
11
|
+
- https://github.com/thecodrr/alfaaz, for insanely fast word/line-counting functions
|
|
11
12
|
|
|
12
13
|
### Related References & Resources
|
|
13
14
|
- https://github.com/heavycircle/remark-obsidian, for mostly wiki link alias & highlights & callouts parsing
|
package/dist/module.json
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { standardKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
|
15
15
|
import { defaultHighlightStyle, syntaxHighlighting, indentOnInput, foldGutter, syntaxTree } from "@codemirror/language";
|
|
16
16
|
import { Compartment, RangeSetBuilder } from "@codemirror/state";
|
|
17
|
+
import { LanguageSupport, LRLanguage } from "@codemirror/language";
|
|
17
18
|
import { languages } from "@codemirror/language-data";
|
|
18
19
|
import wysiwyg from "../editor/wysiwyg";
|
|
19
20
|
import { internalLinkMapFacet } from "../editor/plugins/linkMappingConfig";
|
|
@@ -43,10 +44,17 @@ const editorElement = ref();
|
|
|
43
44
|
const keymaps = computed(() => {
|
|
44
45
|
return props.disabled ? keymap.of([]) : keymap.of([...standardKeymap, ...historyKeymap, indentWithTab]);
|
|
45
46
|
});
|
|
47
|
+
async function loadLanguage(info) {
|
|
48
|
+
const lang = languages.find((l) => l.name.toLowerCase() === info.toLowerCase() || l.alias.map((a) => a.toLowerCase()).includes(info.toLowerCase()));
|
|
49
|
+
if (lang) {
|
|
50
|
+
return await lang.load();
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Language ${info} not found`);
|
|
53
|
+
}
|
|
46
54
|
onMounted(() => {
|
|
47
55
|
const wysiwygPlugin = wysiwyg({
|
|
48
56
|
lezer: {
|
|
49
|
-
codeLanguages:
|
|
57
|
+
codeLanguages: loadLanguage
|
|
50
58
|
}
|
|
51
59
|
});
|
|
52
60
|
extensions.value = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EditorView } from '@codemirror/view';
|
|
2
|
-
import type { InternalLink, SpecialCodeBlockMapping } from '#codemirror-rich-obsidian-editor/editor-types';
|
|
2
|
+
import type { InternalLink, SpecialCodeBlockMapping, InternalLinkClickDetail, ExternalLinkClickDetail } from '#codemirror-rich-obsidian-editor/editor-types';
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
class?: string;
|
|
5
5
|
internalLinkMap?: InternalLink[];
|
|
@@ -15,12 +15,12 @@ type __VLS_PublicProps = __VLS_Props & {
|
|
|
15
15
|
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
16
16
|
view: import("vue").ShallowRef<EditorView | undefined, EditorView | undefined>;
|
|
17
17
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
|
-
"internal-link-click": (
|
|
19
|
-
"external-link-click": (
|
|
20
|
-
"update:modelValue": (value: string | undefined) =>
|
|
18
|
+
"internal-link-click": (detail: InternalLinkClickDetail) => any;
|
|
19
|
+
"external-link-click": (detail: ExternalLinkClickDetail) => any;
|
|
20
|
+
"update:modelValue": (value: string | undefined) => any;
|
|
21
21
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
22
|
-
"onInternal-link-click"?: ((
|
|
23
|
-
"onExternal-link-click"?: ((
|
|
22
|
+
"onInternal-link-click"?: ((detail: InternalLinkClickDetail) => any) | undefined;
|
|
23
|
+
"onExternal-link-click"?: ((detail: ExternalLinkClickDetail) => any) | undefined;
|
|
24
24
|
"onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
|
|
25
25
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
26
|
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function useDocumentUtils(): {
|
|
2
|
+
getWordCount: (text: string) => number;
|
|
3
|
+
getLineCount: (text: string) => number;
|
|
4
|
+
getCharacters: (text: string) => number;
|
|
5
|
+
getReadingTime: (text: string, wordsPerMinute?: number) => number;
|
|
6
|
+
getParagraphs: (text: string) => number;
|
|
7
|
+
getAvgWordLength: (text: string) => number;
|
|
8
|
+
isEmpty: (text: string) => boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { countWords, countLines } from "alfaaz";
|
|
2
|
+
export function useDocumentUtils() {
|
|
3
|
+
function getWordCount(text) {
|
|
4
|
+
return countWords(text);
|
|
5
|
+
}
|
|
6
|
+
function getLineCount(text) {
|
|
7
|
+
return countLines(text);
|
|
8
|
+
}
|
|
9
|
+
function getCharacters(text) {
|
|
10
|
+
return text.length;
|
|
11
|
+
}
|
|
12
|
+
function getReadingTime(text, wordsPerMinute = 200) {
|
|
13
|
+
const wordCount = countWords(text);
|
|
14
|
+
return Math.ceil(wordCount / wordsPerMinute);
|
|
15
|
+
}
|
|
16
|
+
function getParagraphs(text) {
|
|
17
|
+
return text.split(/\n\s*\n/).filter((p) => p.trim().length > 0).length;
|
|
18
|
+
}
|
|
19
|
+
function getAvgWordLength(text) {
|
|
20
|
+
const words = text.match(/\b\w+\b/g) || [];
|
|
21
|
+
if (words.length === 0) return 0;
|
|
22
|
+
const totalChars = words.reduce((sum, word) => sum + word.length, 0);
|
|
23
|
+
return totalChars / words.length;
|
|
24
|
+
}
|
|
25
|
+
function isEmpty(text) {
|
|
26
|
+
return text.trim().length === 0;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
getWordCount,
|
|
30
|
+
getLineCount,
|
|
31
|
+
getCharacters,
|
|
32
|
+
getReadingTime,
|
|
33
|
+
getParagraphs,
|
|
34
|
+
getAvgWordLength,
|
|
35
|
+
isEmpty
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
2
|
import { markdown } from "@codemirror/lang-markdown";
|
|
3
|
+
import { CustomOFM } from "../../runtime/editor/lezer-parsers/customOFMParsers";
|
|
4
|
+
import { GFM } from "@lezer/markdown";
|
|
3
5
|
export function useEditorUtils(editor) {
|
|
4
6
|
const view = computed(() => {
|
|
5
7
|
const instance = unref(editor);
|
|
@@ -24,7 +26,13 @@ export function useEditorUtils(editor) {
|
|
|
24
26
|
unref(view)?.dispatch(...specs);
|
|
25
27
|
}
|
|
26
28
|
function parseMarkdownToAST(markdownText) {
|
|
27
|
-
return markdown(
|
|
29
|
+
return markdown({
|
|
30
|
+
extensions: [
|
|
31
|
+
GFM,
|
|
32
|
+
CustomOFM,
|
|
33
|
+
{ remove: ["SetextHeading"] }
|
|
34
|
+
]
|
|
35
|
+
}).language.parser.parse(markdownText);
|
|
28
36
|
}
|
|
29
37
|
function getDocAst() {
|
|
30
38
|
return parseMarkdownToAST(getDoc() || "");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EditorView } from "@codemirror/view";
|
|
2
2
|
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { internalLinkMapFacet } from "../linkMappingConfig.js";
|
|
3
4
|
export const editorLinkClickPlugin = EditorView.domEventHandlers({
|
|
4
5
|
mousedown(event, view) {
|
|
5
6
|
let target = event.target;
|
|
@@ -21,21 +22,36 @@ export const editorLinkClickPlugin = EditorView.domEventHandlers({
|
|
|
21
22
|
}
|
|
22
23
|
if (anchor.dataset.internalLink === "true") {
|
|
23
24
|
event.preventDefault();
|
|
25
|
+
const path = anchor.dataset.path;
|
|
26
|
+
if (!path) return true;
|
|
27
|
+
const linkMap = view.state.facet(internalLinkMapFacet);
|
|
28
|
+
const linkInfo = linkMap.find((l) => l.internalLinkName === path);
|
|
29
|
+
const type = anchor.dataset.type;
|
|
30
|
+
const detail = {
|
|
31
|
+
path,
|
|
32
|
+
subpath: anchor.dataset.subpath,
|
|
33
|
+
display: anchor.dataset.display,
|
|
34
|
+
type: type || "internal-link",
|
|
35
|
+
redirectToPath: linkInfo?.redirectToPath
|
|
36
|
+
};
|
|
24
37
|
view.dom.dispatchEvent(new CustomEvent("internal-link-click", {
|
|
25
38
|
bubbles: true,
|
|
26
39
|
composed: true,
|
|
27
|
-
detail
|
|
28
|
-
path: anchor.dataset.path,
|
|
29
|
-
subpath: anchor.dataset.subpath,
|
|
30
|
-
display: anchor.dataset.display,
|
|
31
|
-
type: anchor.dataset.type
|
|
32
|
-
}
|
|
40
|
+
detail
|
|
33
41
|
}));
|
|
34
42
|
} else if (anchor.dataset.externalLink === "true") {
|
|
35
43
|
event.preventDefault();
|
|
36
44
|
const url = anchor.dataset.url;
|
|
37
45
|
if (url) {
|
|
38
|
-
|
|
46
|
+
const detail = {
|
|
47
|
+
url,
|
|
48
|
+
text: anchor.textContent
|
|
49
|
+
};
|
|
50
|
+
view.dom.dispatchEvent(new CustomEvent("external-link-click", {
|
|
51
|
+
bubbles: true,
|
|
52
|
+
composed: true,
|
|
53
|
+
detail
|
|
54
|
+
}));
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
return true;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Component } from 'vue'
|
|
2
|
+
import type { LanguageSupport } from '@codemirror/language'
|
|
2
3
|
|
|
3
4
|
export interface InternalLink {
|
|
4
5
|
internalLinkName: string;
|
|
@@ -7,11 +8,27 @@ export interface InternalLink {
|
|
|
7
8
|
embedComponent?: Component;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
export interface InternalLinkClickDetail {
|
|
12
|
+
path: string;
|
|
13
|
+
subpath?: string;
|
|
14
|
+
display?: string;
|
|
15
|
+
type: 'embed' | 'internal-link';
|
|
16
|
+
redirectToPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ExternalLinkClickDetail {
|
|
20
|
+
url: string;
|
|
21
|
+
text: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
export interface SpecialCodeBlockMapping {
|
|
11
25
|
codeInfo: string
|
|
12
26
|
component: Component
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
export type WysiwygPlugin = {
|
|
16
|
-
lezer?:
|
|
30
|
+
lezer?: {
|
|
31
|
+
codeLanguages?: (info: string) => LanguageSupport | Promise<LanguageSupport> | null
|
|
32
|
+
[key: string]: any
|
|
33
|
+
}
|
|
17
34
|
}
|
|
@@ -33,9 +33,10 @@ import { editorLivePreviewField, proseTaskListPlugin } from "./plugins/codemirro
|
|
|
33
33
|
import { proseCalloutPlugin } from "./plugins/codemirror-plugin-proses/proseCalloutPlugin.js";
|
|
34
34
|
import { indentationListPlugin } from "./plugins/codemirror-editor-plugins/indentationListPlugin.js";
|
|
35
35
|
export default function(config) {
|
|
36
|
+
const { codeLanguages, ...lezerRest } = config?.lezer ?? {};
|
|
36
37
|
const mergedConfig = {
|
|
37
|
-
...
|
|
38
|
-
|
|
38
|
+
...lezerRest ?? [],
|
|
39
|
+
codeLanguages,
|
|
39
40
|
extensions: [
|
|
40
41
|
GFM,
|
|
41
42
|
CustomOFM,
|
|
@@ -74,6 +75,7 @@ export default function(config) {
|
|
|
74
75
|
indentationListPlugin,
|
|
75
76
|
// editorAttributesPlugin,
|
|
76
77
|
syntaxHighlighting(proseStylesPlugin),
|
|
78
|
+
//@ts-ignore
|
|
77
79
|
markdown(mergedConfig)
|
|
78
80
|
]
|
|
79
81
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@type32/codemirror-rich-obsidian-editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "OFM Editor Component for Nuxt.",
|
|
5
5
|
"repository": "Type-32/codemirror-rich-obsidian",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"@nuxt/image": "1.10.0",
|
|
47
47
|
"@nuxt/kit": "^4.1.2",
|
|
48
48
|
"@nuxt/ui": "^4.0.0-alpha.1",
|
|
49
|
+
"alfaaz": "^1.1.0",
|
|
49
50
|
"codemirror": "^6.0.2",
|
|
50
51
|
"js-yaml": "^4.1.0",
|
|
51
52
|
"katex": "^0.16.22",
|