@type32/codemirror-rich-obsidian-editor 0.0.3
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 +99 -0
- package/dist/module.d.mts +8 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +25 -0
- package/dist/runtime/assets/css/editor.css +1 -0
- package/dist/runtime/components/Editor/ImageEmbedComponent.vue +11 -0
- package/dist/runtime/components/Editor/ImageEmbedComponent.vue.d.ts +5 -0
- package/dist/runtime/components/Editor/TestCustomCodeBlock.vue +16 -0
- package/dist/runtime/components/Editor/TestCustomCodeBlock.vue.d.ts +5 -0
- package/dist/runtime/components/Editor.client.vue +182 -0
- package/dist/runtime/components/Editor.client.vue.d.ts +23 -0
- package/dist/runtime/editor/lezer-parsers/customOFMParsers.d.ts +1 -0
- package/dist/runtime/editor/lezer-parsers/customOFMParsers.js +23 -0
- package/dist/runtime/editor/lezer-parsers/lezerCalloutParser.d.ts +8 -0
- package/dist/runtime/editor/lezer-parsers/lezerCalloutParser.js +53 -0
- package/dist/runtime/editor/lezer-parsers/lezerHashtagParser.d.ts +6 -0
- package/dist/runtime/editor/lezer-parsers/lezerHashtagParser.js +35 -0
- package/dist/runtime/editor/lezer-parsers/lezerIndentationParser.d.ts +4 -0
- package/dist/runtime/editor/lezer-parsers/lezerIndentationParser.js +23 -0
- package/dist/runtime/editor/lezer-parsers/lezerInternalLinkParser.d.ts +10 -0
- package/dist/runtime/editor/lezer-parsers/lezerInternalLinkParser.js +110 -0
- package/dist/runtime/editor/lezer-parsers/lezerLatexParser.d.ts +7 -0
- package/dist/runtime/editor/lezer-parsers/lezerLatexParser.js +75 -0
- package/dist/runtime/editor/lezer-parsers/lezerYamlFrontmatterParser.d.ts +13 -0
- package/dist/runtime/editor/lezer-parsers/lezerYamlFrontmatterParser.js +55 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/customBracketClosingPlugin.d.ts +4 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/customBracketClosingPlugin.js +94 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorAttributesPlugin.d.ts +8 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorAttributesPlugin.js +136 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorInternalLinkAutocompletePlugin.d.ts +1 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorInternalLinkAutocompletePlugin.js +43 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorKeymapPlugin.d.ts +1 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorKeymapPlugin.js +95 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorLinkClickPlugin.d.ts +1 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/editorLinkClickPlugin.js +43 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/indentationGuidesPlugin.d.ts +10 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/indentationGuidesPlugin.js +121 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/indentationListPlugin.d.ts +5 -0
- package/dist/runtime/editor/plugins/codemirror-editor-plugins/indentationListPlugin.js +66 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseCalloutPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseCalloutPlugin.js +63 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseCodeBlockCodemirrorViewPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseCodeBlockCodemirrorViewPlugin.js +98 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseHashtagCodemirrorViewPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseHashtagCodemirrorViewPlugin.js +27 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseHighlightCodemirrorViewPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseHighlightCodemirrorViewPlugin.js +51 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseInternalLinkCodemirrorViewPlugin.d.ts +6 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseInternalLinkCodemirrorViewPlugin.js +112 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseLatexCodemirrorViewPlugin.d.ts +2 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseLatexCodemirrorViewPlugin.js +160 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseLinkCodemirrorViewPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseLinkCodemirrorViewPlugin.js +98 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseQuoteblockCodemirrorViewPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseQuoteblockCodemirrorViewPlugin.js +76 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseTaskListPlugin.d.ts +8 -0
- package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseTaskListPlugin.js +106 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseCalloutWidget.d.ts +13 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseCalloutWidget.js +87 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseCodeBlockWidgets.d.ts +15 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseCodeBlockWidgets.js +53 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseLatexWidgets.d.ts +11 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseLatexWidgets.js +50 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseVueComponentEmbedWidget.d.ts +12 -0
- package/dist/runtime/editor/plugins/codemirror-widgets/proseVueComponentEmbedWidget.js +32 -0
- package/dist/runtime/editor/plugins/customBracketClosingConfig.d.ts +2 -0
- package/dist/runtime/editor/plugins/customBracketClosingConfig.js +4 -0
- package/dist/runtime/editor/plugins/lezerStylesHighlightingPlugin.d.ts +3 -0
- package/dist/runtime/editor/plugins/lezerStylesHighlightingPlugin.js +55 -0
- package/dist/runtime/editor/plugins/linkMappingConfig.d.ts +3 -0
- package/dist/runtime/editor/plugins/linkMappingConfig.js +4 -0
- package/dist/runtime/editor/plugins/richTextPlugin.d.ts +10 -0
- package/dist/runtime/editor/plugins/richTextPlugin.js +94 -0
- package/dist/runtime/editor/plugins/specialCodeBlockMappingConfig.d.ts +3 -0
- package/dist/runtime/editor/plugins/specialCodeBlockMappingConfig.js +4 -0
- package/dist/runtime/editor/types/editor-types.d.ts +17 -0
- package/dist/runtime/editor/utility/decorations.d.ts +9 -0
- package/dist/runtime/editor/utility/decorations.js +9 -0
- package/dist/runtime/editor/utility/tools.d.ts +12 -0
- package/dist/runtime/editor/utility/tools.js +32 -0
- package/dist/runtime/editor/wysiwyg.d.ts +4 -0
- package/dist/runtime/editor/wysiwyg.js +80 -0
- package/dist/types.d.mts +3 -0
- package/package.json +80 -0
package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseLatexCodemirrorViewPlugin.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { EditorView, ViewPlugin, Decoration } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import { StateField } from "@codemirror/state";
|
|
4
|
+
import { BlockLatexWidget, InlineLatexWidget } from "../codemirror-widgets/proseLatexWidgets.js";
|
|
5
|
+
import { cursorSelectionCoveredNode, toCursorNodePositions } from "../../utility/tools.js";
|
|
6
|
+
function decorate(state) {
|
|
7
|
+
const decorations = [];
|
|
8
|
+
const tree = syntaxTree(state);
|
|
9
|
+
const cursor = state.selection.main;
|
|
10
|
+
tree.iterate({
|
|
11
|
+
enter: (node) => {
|
|
12
|
+
if (node.name === "TexBlock") {
|
|
13
|
+
const isNodeRangeActive = (nodeFrom, nodeTo) => {
|
|
14
|
+
if (cursor.empty) {
|
|
15
|
+
return cursor.from >= nodeFrom && cursor.from <= nodeTo;
|
|
16
|
+
} else {
|
|
17
|
+
return Math.max(nodeFrom, cursor.from) < Math.min(nodeTo, cursor.to);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const poses = toCursorNodePositions(state, node);
|
|
21
|
+
if (isNodeRangeActive(node.from, node.to) || cursorSelectionCoveredNode(poses.cursorFrom, poses.cursorTo, poses.nodeFrom, poses.nodeTo)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const content = state.doc.sliceString(node.from + 2, node.to - 2);
|
|
25
|
+
const nodeText = state.doc.sliceString(node.from, node.to);
|
|
26
|
+
const hasLineBreaks = nodeText.includes("\n");
|
|
27
|
+
if (hasLineBreaks) {
|
|
28
|
+
decorations.push(Decoration.mark({ class: "cm-hidden-latex" }).range(node.from, node.to));
|
|
29
|
+
const line = state.doc.lineAt(node.to);
|
|
30
|
+
const widget = Decoration.widget({
|
|
31
|
+
widget: new BlockLatexWidget(content),
|
|
32
|
+
block: true,
|
|
33
|
+
side: 1
|
|
34
|
+
});
|
|
35
|
+
decorations.push(widget.range(line.to));
|
|
36
|
+
} else {
|
|
37
|
+
const deco = Decoration.replace({
|
|
38
|
+
widget: new BlockLatexWidget(content)
|
|
39
|
+
});
|
|
40
|
+
decorations.push(deco.range(node.from, node.to));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return Decoration.set(decorations);
|
|
46
|
+
}
|
|
47
|
+
function decorateInline(view) {
|
|
48
|
+
const decorations = [];
|
|
49
|
+
const tree = syntaxTree(view.state);
|
|
50
|
+
const cursor = view.state.selection.main;
|
|
51
|
+
tree.iterate({
|
|
52
|
+
enter: (node) => {
|
|
53
|
+
if (node.name === "TexInline") {
|
|
54
|
+
const isNodeRangeActive = (nodeFrom, nodeTo) => {
|
|
55
|
+
if (cursor.empty) {
|
|
56
|
+
return cursor.from >= nodeFrom && cursor.from <= nodeTo;
|
|
57
|
+
} else {
|
|
58
|
+
return Math.max(nodeFrom, cursor.from) < Math.min(nodeTo, cursor.to);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
if (isNodeRangeActive(node.from, node.to)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const content = view.state.doc.sliceString(node.from + 1, node.to - 1);
|
|
65
|
+
const deco = Decoration.replace({
|
|
66
|
+
widget: new InlineLatexWidget(content)
|
|
67
|
+
});
|
|
68
|
+
decorations.push(deco.range(node.from, node.to));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return Decoration.set(decorations);
|
|
73
|
+
}
|
|
74
|
+
const latexBlockPlugin = StateField.define({
|
|
75
|
+
create(state) {
|
|
76
|
+
return decorate(state);
|
|
77
|
+
},
|
|
78
|
+
update(value, tr) {
|
|
79
|
+
if (tr.docChanged || tr.selection) {
|
|
80
|
+
return decorate(tr.state);
|
|
81
|
+
}
|
|
82
|
+
return value.map(tr.changes);
|
|
83
|
+
},
|
|
84
|
+
provide: (f) => EditorView.decorations.from(f)
|
|
85
|
+
});
|
|
86
|
+
const latexInlinePlugin = ViewPlugin.fromClass(
|
|
87
|
+
class {
|
|
88
|
+
decorations;
|
|
89
|
+
constructor(view) {
|
|
90
|
+
this.decorations = this.buildDecorations(view);
|
|
91
|
+
}
|
|
92
|
+
update(update) {
|
|
93
|
+
if (update.docChanged) {
|
|
94
|
+
this.decorations = this.decorations.map(update.changes);
|
|
95
|
+
update.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
|
96
|
+
const newWidgets = this.buildDecorationsForRange(
|
|
97
|
+
update.view,
|
|
98
|
+
fromB,
|
|
99
|
+
toB
|
|
100
|
+
);
|
|
101
|
+
this.decorations = this.decorations.update({
|
|
102
|
+
filter: (f, t) => f < fromB || t > toB,
|
|
103
|
+
add: newWidgets,
|
|
104
|
+
sort: true
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
} else if (update.viewportChanged || update.selectionSet) {
|
|
108
|
+
this.decorations = this.buildDecorations(update.view);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
buildDecorations(view) {
|
|
112
|
+
const decorations = [];
|
|
113
|
+
for (const { from, to } of view.visibleRanges) {
|
|
114
|
+
decorations.push(...this.buildDecorationsForRange(view, from, to));
|
|
115
|
+
}
|
|
116
|
+
return Decoration.set(decorations);
|
|
117
|
+
}
|
|
118
|
+
buildDecorationsForRange(view, from, to) {
|
|
119
|
+
const decorations = [];
|
|
120
|
+
const tree = syntaxTree(view.state);
|
|
121
|
+
const cursor = view.state.selection.main;
|
|
122
|
+
tree.iterate({
|
|
123
|
+
from,
|
|
124
|
+
to,
|
|
125
|
+
enter: (node) => {
|
|
126
|
+
if (node.name === "TexInline") {
|
|
127
|
+
const isNodeRangeActive = (nodeFrom, nodeTo) => {
|
|
128
|
+
if (cursor.empty) {
|
|
129
|
+
return cursor.from >= nodeFrom && cursor.from <= nodeTo;
|
|
130
|
+
} else {
|
|
131
|
+
return Math.max(nodeFrom, cursor.from) < Math.min(nodeTo, cursor.to);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
if (isNodeRangeActive(node.from, node.to)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const content = view.state.doc.sliceString(
|
|
138
|
+
node.from + 1,
|
|
139
|
+
node.to - 1
|
|
140
|
+
);
|
|
141
|
+
const deco = Decoration.replace({
|
|
142
|
+
widget: new InlineLatexWidget(content)
|
|
143
|
+
});
|
|
144
|
+
decorations.push(deco.range(node.from, node.to));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return decorations;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
decorations: (v) => v.decorations
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
export function proseLatexCodemirrorViewPlugin() {
|
|
156
|
+
return [
|
|
157
|
+
latexBlockPlugin,
|
|
158
|
+
latexInlinePlugin
|
|
159
|
+
];
|
|
160
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Decoration, EditorView } from "@codemirror/view";
|
|
2
|
+
import { StateField, RangeSet } from "@codemirror/state";
|
|
3
|
+
import { syntaxTree } from "@codemirror/language";
|
|
4
|
+
import { cursorSelectionCoveredNode, isNodeRangeActive, toCursorNodePositions } from "../../utility/tools.js";
|
|
5
|
+
import { ProseVueComponentEmbedWidget } from "../codemirror-widgets/proseVueComponentEmbedWidget.js";
|
|
6
|
+
import ImageEmbedComponent from "../../../components/Editor/ImageEmbedComponent.vue";
|
|
7
|
+
function buildLinkDecorations(state) {
|
|
8
|
+
const decorations = [];
|
|
9
|
+
const widgets = [];
|
|
10
|
+
syntaxTree(state).iterate({
|
|
11
|
+
enter({ node }) {
|
|
12
|
+
if (node.name === "Image") {
|
|
13
|
+
const poses = toCursorNodePositions(state, node);
|
|
14
|
+
const isActive = isNodeRangeActive(state, node.from, node.to) || cursorSelectionCoveredNode(poses.cursorFrom, poses.cursorTo, poses.nodeFrom, poses.nodeTo);
|
|
15
|
+
if (!isActive) {
|
|
16
|
+
const urlNode = node.getChild("URL");
|
|
17
|
+
if (urlNode) {
|
|
18
|
+
const url = state.doc.sliceString(urlNode.from, urlNode.to);
|
|
19
|
+
decorations.push(Decoration.replace({
|
|
20
|
+
widget: new ProseVueComponentEmbedWidget(ImageEmbedComponent, { filePath: url }, node.from),
|
|
21
|
+
block: true
|
|
22
|
+
}).range(node.from, node.to));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (node.name === "Link") {
|
|
28
|
+
const poses = toCursorNodePositions(state, node);
|
|
29
|
+
const isActive = isNodeRangeActive(state, node.from, node.to) || cursorSelectionCoveredNode(poses.cursorFrom, poses.cursorTo, poses.nodeFrom, poses.nodeTo);
|
|
30
|
+
if (!isActive) {
|
|
31
|
+
const allMarks = node.getChildren("LinkMark");
|
|
32
|
+
const urlNode = node.getChild("URL");
|
|
33
|
+
const openBracket = allMarks.find((m) => state.doc.sliceString(m.from, m.to) === "[");
|
|
34
|
+
const closeBracket = allMarks.find((m) => state.doc.sliceString(m.from, m.to) === "]");
|
|
35
|
+
if (urlNode && openBracket && closeBracket) {
|
|
36
|
+
const linkTextStart = openBracket.to;
|
|
37
|
+
const linkTextEnd = closeBracket.from;
|
|
38
|
+
const url = state.doc.sliceString(urlNode.from, urlNode.to);
|
|
39
|
+
const text = state.doc.sliceString(linkTextStart, linkTextEnd);
|
|
40
|
+
const linkAttributes = {
|
|
41
|
+
"href": url,
|
|
42
|
+
"target": "_blank",
|
|
43
|
+
"class": "cm-clickable-link",
|
|
44
|
+
"data-external-link": "true",
|
|
45
|
+
"data-url": url,
|
|
46
|
+
"data-text": text
|
|
47
|
+
};
|
|
48
|
+
decorations.push(Decoration.replace({}).range(node.from, linkTextStart));
|
|
49
|
+
decorations.push(Decoration.replace({}).range(linkTextEnd, node.to));
|
|
50
|
+
decorations.push(Decoration.mark({
|
|
51
|
+
tagName: "a",
|
|
52
|
+
attributes: linkAttributes
|
|
53
|
+
}).range(linkTextStart, linkTextEnd));
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (node.name === "URL") {
|
|
60
|
+
const url = state.doc.sliceString(node.from, node.to);
|
|
61
|
+
const isImage = /\.(png|jpg|jpeg|gif|svg|webp)$/i.test(url) || url.includes("picsum.photos");
|
|
62
|
+
if (isImage) {
|
|
63
|
+
const line = state.doc.lineAt(node.from);
|
|
64
|
+
widgets.push(Decoration.widget({
|
|
65
|
+
widget: new ProseVueComponentEmbedWidget(ImageEmbedComponent, { filePath: url }, node.from),
|
|
66
|
+
block: true,
|
|
67
|
+
side: 1
|
|
68
|
+
}).range(line.to));
|
|
69
|
+
}
|
|
70
|
+
if (!isNodeRangeActive(state, node.from, node.to)) {
|
|
71
|
+
decorations.push(Decoration.mark({
|
|
72
|
+
tagName: "a",
|
|
73
|
+
attributes: {
|
|
74
|
+
href: url,
|
|
75
|
+
target: "_blank",
|
|
76
|
+
class: "cm-clickable-link",
|
|
77
|
+
"data-external-link": "true",
|
|
78
|
+
"data-url": url
|
|
79
|
+
}
|
|
80
|
+
}).range(node.from, node.to));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return [...decorations, ...widgets];
|
|
86
|
+
}
|
|
87
|
+
export const proseLinkCodemirrorViewPlugin = StateField.define({
|
|
88
|
+
create(state) {
|
|
89
|
+
return RangeSet.of(buildLinkDecorations(state), true);
|
|
90
|
+
},
|
|
91
|
+
update(value, tr) {
|
|
92
|
+
if (tr.docChanged || tr.selection) {
|
|
93
|
+
return RangeSet.of(buildLinkDecorations(tr.state), true);
|
|
94
|
+
}
|
|
95
|
+
return value.map(tr.changes);
|
|
96
|
+
},
|
|
97
|
+
provide: (f) => EditorView.decorations.from(f)
|
|
98
|
+
});
|
package/dist/runtime/editor/plugins/codemirror-plugin-proses/proseQuoteblockCodemirrorViewPlugin.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Decoration, EditorView } from "@codemirror/view";
|
|
2
|
+
import { StateField, RangeSet } from "@codemirror/state";
|
|
3
|
+
import { syntaxTree } from "@codemirror/language";
|
|
4
|
+
import { cursorSelectionCoveredNode } from "../../utility/tools.js";
|
|
5
|
+
function isCursorOnLine(state, lineStart, lineEnd) {
|
|
6
|
+
const cursor = state.selection.main;
|
|
7
|
+
return cursor.from >= lineStart && cursor.from <= lineEnd;
|
|
8
|
+
}
|
|
9
|
+
function buildQuoteblockDecorations(state) {
|
|
10
|
+
const decorations = [];
|
|
11
|
+
syntaxTree(state).iterate({
|
|
12
|
+
enter(node) {
|
|
13
|
+
if (node.name === "Blockquote") {
|
|
14
|
+
const firstLine = state.doc.lineAt(node.from);
|
|
15
|
+
const lastLine = state.doc.lineAt(node.to);
|
|
16
|
+
const lineCount = lastLine.number - firstLine.number + 1;
|
|
17
|
+
const [cursor] = state.selection.ranges;
|
|
18
|
+
const cursorFrom = cursor?.from || 0, cursorTo = cursor?.to || 0, nodeFrom = node.from || 0, nodeTo = node.to || 0;
|
|
19
|
+
for (let i = firstLine.number; i <= lastLine.number; i++) {
|
|
20
|
+
const line = state.doc.line(i);
|
|
21
|
+
let lineClass = "cm-quoteblock";
|
|
22
|
+
if (lineCount === 1) {
|
|
23
|
+
lineClass += " cm-quoteblock-single";
|
|
24
|
+
} else if (i === firstLine.number) {
|
|
25
|
+
lineClass += " cm-quoteblock-start";
|
|
26
|
+
} else if (i === lastLine.number) {
|
|
27
|
+
lineClass += " cm-quoteblock-end";
|
|
28
|
+
} else {
|
|
29
|
+
lineClass += " cm-quoteblock-middle";
|
|
30
|
+
}
|
|
31
|
+
decorations.push(
|
|
32
|
+
Decoration.line({
|
|
33
|
+
attributes: { class: lineClass }
|
|
34
|
+
}).range(line.from)
|
|
35
|
+
);
|
|
36
|
+
const isCursorActive = isCursorOnLine(state, line.from, line.to) || cursorSelectionCoveredNode(cursorFrom, cursorTo, nodeFrom, nodeTo);
|
|
37
|
+
const lineText = line.text;
|
|
38
|
+
const markMatch = lineText.match(/^(\s*>+)/);
|
|
39
|
+
if (markMatch) {
|
|
40
|
+
const markEnd = line.from + markMatch[0].length;
|
|
41
|
+
let markClass = "cm-formatting cm-formatting-quote cm-meta";
|
|
42
|
+
if (isCursorActive) {
|
|
43
|
+
markClass += " cm-formatting-quote-active";
|
|
44
|
+
}
|
|
45
|
+
decorations.push(
|
|
46
|
+
Decoration.mark({
|
|
47
|
+
class: markClass
|
|
48
|
+
}).range(line.from, markEnd)
|
|
49
|
+
);
|
|
50
|
+
if (markEnd < line.to) {
|
|
51
|
+
decorations.push(
|
|
52
|
+
Decoration.mark({
|
|
53
|
+
class: "cm-quote"
|
|
54
|
+
}).range(markEnd, line.to)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return decorations;
|
|
64
|
+
}
|
|
65
|
+
export const proseQuoteblockCodemirrorViewPlugin = StateField.define({
|
|
66
|
+
create(state) {
|
|
67
|
+
return RangeSet.of(buildQuoteblockDecorations(state), true);
|
|
68
|
+
},
|
|
69
|
+
update(value, tr) {
|
|
70
|
+
if (tr.docChanged || tr.selection) {
|
|
71
|
+
return RangeSet.of(buildQuoteblockDecorations(tr.state), true);
|
|
72
|
+
}
|
|
73
|
+
return value.map(tr.changes);
|
|
74
|
+
},
|
|
75
|
+
provide: (f) => EditorView.decorations.from(f)
|
|
76
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
2
|
+
import { StateField } from '@codemirror/state';
|
|
3
|
+
export declare const editorLivePreviewField: StateField<boolean>;
|
|
4
|
+
export declare const proseTaskListPlugin: ViewPlugin<{
|
|
5
|
+
decorations: import("@codemirror/view").DecorationSet;
|
|
6
|
+
update(update: ViewUpdate): void;
|
|
7
|
+
buildDecorations(view: EditorView): import("@codemirror/view").DecorationSet;
|
|
8
|
+
}, undefined>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Decoration, ViewPlugin, WidgetType } from "@codemirror/view";
|
|
2
|
+
import { StateField } from "@codemirror/state";
|
|
3
|
+
import { syntaxTree } from "@codemirror/language";
|
|
4
|
+
import { isCursorInRange } from "../../utility/tools.js";
|
|
5
|
+
export const editorLivePreviewField = StateField.define({
|
|
6
|
+
create: () => true,
|
|
7
|
+
update: (value) => value
|
|
8
|
+
});
|
|
9
|
+
class CheckboxWidget extends WidgetType {
|
|
10
|
+
constructor(checked, from, to) {
|
|
11
|
+
super();
|
|
12
|
+
this.checked = checked;
|
|
13
|
+
this.from = from;
|
|
14
|
+
this.to = to;
|
|
15
|
+
}
|
|
16
|
+
eq(other) {
|
|
17
|
+
return other.checked === this.checked && other.from === this.from && other.to === this.to;
|
|
18
|
+
}
|
|
19
|
+
toDOM(view) {
|
|
20
|
+
const checkbox = document.createElement("div");
|
|
21
|
+
checkbox.classList.add("cm-task-checkbox-wrapper");
|
|
22
|
+
if (this.checked && this.checked.toLowerCase() === "x") {
|
|
23
|
+
checkbox.dataset.checked = "true";
|
|
24
|
+
} else if (this.checked && this.checked !== " ") {
|
|
25
|
+
checkbox.dataset.special = this.checked;
|
|
26
|
+
checkbox.innerText = this.checked;
|
|
27
|
+
} else {
|
|
28
|
+
checkbox.dataset.checked = "false";
|
|
29
|
+
}
|
|
30
|
+
checkbox.onclick = (event) => {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
const isChecked = checkbox.dataset.checked === "true";
|
|
33
|
+
const char = checkbox.dataset.special ? " " : !isChecked ? "x" : " ";
|
|
34
|
+
view.dispatch({
|
|
35
|
+
changes: {
|
|
36
|
+
from: this.from + 1,
|
|
37
|
+
to: this.to - 1,
|
|
38
|
+
insert: char
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
return checkbox;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const proseTaskListPlugin = ViewPlugin.fromClass(
|
|
46
|
+
class {
|
|
47
|
+
decorations = Decoration.none;
|
|
48
|
+
constructor(view) {
|
|
49
|
+
this.decorations = this.buildDecorations(view);
|
|
50
|
+
}
|
|
51
|
+
update(update) {
|
|
52
|
+
if (update.docChanged || update.viewportChanged || update.selectionSet) {
|
|
53
|
+
this.decorations = this.buildDecorations(update.view);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
buildDecorations(view) {
|
|
57
|
+
const decorations = /* @__PURE__ */ new Set();
|
|
58
|
+
const { state } = view;
|
|
59
|
+
const isLivePreview = state.field(editorLivePreviewField);
|
|
60
|
+
for (const { from, to } of view.visibleRanges) {
|
|
61
|
+
syntaxTree(state).iterate({
|
|
62
|
+
from,
|
|
63
|
+
to,
|
|
64
|
+
enter: (node) => {
|
|
65
|
+
if (node.type.name !== "Task") return;
|
|
66
|
+
const { from: nodeFrom, to: nodeTo } = node;
|
|
67
|
+
const listItem = node.node.parent;
|
|
68
|
+
if (!listItem || listItem.type.name !== "ListItem") return;
|
|
69
|
+
const listMark = listItem.getChild("ListMark");
|
|
70
|
+
if (!listMark) return;
|
|
71
|
+
const taskMarker = node.node.getChild("TaskMarker");
|
|
72
|
+
if (!taskMarker) return;
|
|
73
|
+
const checkedChar = state.doc.sliceString(
|
|
74
|
+
taskMarker.from + 1,
|
|
75
|
+
taskMarker.to - 1
|
|
76
|
+
);
|
|
77
|
+
const isChecked = checkedChar && checkedChar.toLowerCase() === "x";
|
|
78
|
+
if (isChecked) {
|
|
79
|
+
const line = state.doc.lineAt(listItem.from);
|
|
80
|
+
decorations.add(
|
|
81
|
+
Decoration.line({
|
|
82
|
+
attributes: { class: "cm-task-checked" }
|
|
83
|
+
}).range(line.from)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (isLivePreview && (isCursorInRange(state, [listMark.from, listMark.to]) || isCursorInRange(state, [taskMarker.from, taskMarker.to])))
|
|
87
|
+
return;
|
|
88
|
+
decorations.add(
|
|
89
|
+
Decoration.replace({
|
|
90
|
+
widget: new CheckboxWidget(
|
|
91
|
+
state.doc.sliceString(taskMarker.from + 1, taskMarker.to - 1),
|
|
92
|
+
taskMarker.from,
|
|
93
|
+
taskMarker.to
|
|
94
|
+
)
|
|
95
|
+
}).range(listMark.from, taskMarker.to)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return Decoration.set([...decorations]);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
decorations: (value) => value.decorations
|
|
105
|
+
}
|
|
106
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WidgetType } from '@codemirror/view';
|
|
2
|
+
import type { EditorView } from '@codemirror/view';
|
|
3
|
+
export declare class CalloutWidget extends WidgetType {
|
|
4
|
+
private sourceNodeFrom;
|
|
5
|
+
private sourceNodeTo;
|
|
6
|
+
private rawContent;
|
|
7
|
+
constructor(sourceNodeFrom: number, sourceNodeTo: number);
|
|
8
|
+
private findParentBlockquote;
|
|
9
|
+
private extractDetails;
|
|
10
|
+
toDOM(view: EditorView): HTMLElement;
|
|
11
|
+
eq(other: CalloutWidget): boolean;
|
|
12
|
+
ignoreEvent(event: Event): boolean;
|
|
13
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { WidgetType } from "@codemirror/view";
|
|
2
|
+
import { syntaxTree } from "@codemirror/language";
|
|
3
|
+
import MarkdownIt from "markdown-it";
|
|
4
|
+
import markdownItObsidianCallouts from "markdown-it-obsidian-callouts";
|
|
5
|
+
const md = new MarkdownIt({ html: true }).use(markdownItObsidianCallouts);
|
|
6
|
+
export class CalloutWidget extends WidgetType {
|
|
7
|
+
constructor(sourceNodeFrom, sourceNodeTo) {
|
|
8
|
+
super();
|
|
9
|
+
this.sourceNodeFrom = sourceNodeFrom;
|
|
10
|
+
this.sourceNodeTo = sourceNodeTo;
|
|
11
|
+
}
|
|
12
|
+
rawContent = "";
|
|
13
|
+
findParentBlockquote(node) {
|
|
14
|
+
let current = node;
|
|
15
|
+
while (current) {
|
|
16
|
+
if (current.name === "Blockquote") {
|
|
17
|
+
return current;
|
|
18
|
+
}
|
|
19
|
+
current = current.parent;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
extractDetails(view) {
|
|
24
|
+
const state = view.state;
|
|
25
|
+
const calloutNode = syntaxTree(state).resolve(this.sourceNodeFrom, 1);
|
|
26
|
+
const parentBlockquote = this.findParentBlockquote(calloutNode);
|
|
27
|
+
if (parentBlockquote) {
|
|
28
|
+
this.rawContent = view.state.doc.sliceString(parentBlockquote.from, parentBlockquote.to);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
toDOM(view) {
|
|
32
|
+
this.extractDetails(view);
|
|
33
|
+
const renderedHtml = md.render(this.rawContent);
|
|
34
|
+
const tempDiv = document.createElement("div");
|
|
35
|
+
tempDiv.innerHTML = renderedHtml;
|
|
36
|
+
const calloutEl = tempDiv.querySelector(".callout");
|
|
37
|
+
if (!calloutEl) {
|
|
38
|
+
const fallback = document.createElement("div");
|
|
39
|
+
fallback.className = `cm-callout-widget callout callout-error`;
|
|
40
|
+
fallback.textContent = "Error rendering callout";
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
calloutEl.classList.add("cm-callout-widget");
|
|
44
|
+
calloutEl.setAttribute("contenteditable", "false");
|
|
45
|
+
const editButton = document.createElement("div");
|
|
46
|
+
editButton.className = "edit-block-button";
|
|
47
|
+
editButton.setAttribute("aria-label", "Edit this block");
|
|
48
|
+
editButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 16 4-4-4-4"></path><path d="m6 8-4 4 4 4"></path><path d="m14.5 4-5 16"></path></svg>`;
|
|
49
|
+
editButton.onmousedown = (e) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
};
|
|
53
|
+
editButton.onclick = (e) => {
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
const parentBlockquote = this.findParentBlockquote(syntaxTree(view.state).resolve(this.sourceNodeFrom, 1));
|
|
56
|
+
if (parentBlockquote) {
|
|
57
|
+
view.dispatch({ selection: { anchor: parentBlockquote.from } });
|
|
58
|
+
view.focus();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const titleDiv = calloutEl.querySelector(".callout-title");
|
|
62
|
+
if (titleDiv) {
|
|
63
|
+
const foldDiv = calloutEl.querySelector(".callout-fold");
|
|
64
|
+
if (foldDiv) {
|
|
65
|
+
const foldIcon = document.createElement("svg");
|
|
66
|
+
foldIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>`;
|
|
67
|
+
foldDiv.appendChild(foldIcon);
|
|
68
|
+
}
|
|
69
|
+
titleDiv.appendChild(editButton);
|
|
70
|
+
} else {
|
|
71
|
+
calloutEl.prepend(editButton);
|
|
72
|
+
}
|
|
73
|
+
return calloutEl;
|
|
74
|
+
}
|
|
75
|
+
eq(other) {
|
|
76
|
+
return other.sourceNodeFrom === this.sourceNodeFrom && other.sourceNodeTo === this.sourceNodeTo && other.rawContent === this.rawContent;
|
|
77
|
+
}
|
|
78
|
+
ignoreEvent(event) {
|
|
79
|
+
if (event.type === "click" || event.type === "mousedown") {
|
|
80
|
+
const target = event.target;
|
|
81
|
+
if (target.closest(".edit-block-button") || target.closest(".callout-fold")) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WidgetType } from '@codemirror/view';
|
|
2
|
+
import type { EditorView } from '@codemirror/view';
|
|
3
|
+
export declare class LanguageFlairWidget extends WidgetType {
|
|
4
|
+
private lang;
|
|
5
|
+
private codeContent;
|
|
6
|
+
private button;
|
|
7
|
+
constructor(lang: string, codeContent: string);
|
|
8
|
+
toDOM(view: EditorView): HTMLElement;
|
|
9
|
+
eq(other: LanguageFlairWidget): boolean;
|
|
10
|
+
ignoreEvent(event: Event): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class EndFenceWidget extends WidgetType {
|
|
13
|
+
toDOM(view: EditorView): HTMLElement;
|
|
14
|
+
ignoreEvent(): boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { WidgetType } from "@codemirror/view";
|
|
2
|
+
export class LanguageFlairWidget extends WidgetType {
|
|
3
|
+
constructor(lang, codeContent) {
|
|
4
|
+
super();
|
|
5
|
+
this.lang = lang;
|
|
6
|
+
this.codeContent = codeContent;
|
|
7
|
+
}
|
|
8
|
+
button = null;
|
|
9
|
+
toDOM(view) {
|
|
10
|
+
const widgetRoot = document.createElement("div");
|
|
11
|
+
widgetRoot.className = "cm-codeblock-flair-container";
|
|
12
|
+
widgetRoot.setAttribute("contenteditable", "false");
|
|
13
|
+
this.button = document.createElement("button");
|
|
14
|
+
this.button.className = "cm-codeblock-copy-button";
|
|
15
|
+
this.button.textContent = this.lang || "";
|
|
16
|
+
this.button.onclick = (event) => {
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
navigator.clipboard.writeText(this.codeContent).then(() => {
|
|
19
|
+
if (this.button) {
|
|
20
|
+
this.button.disabled = true;
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (this.button) {
|
|
23
|
+
this.button.disabled = false;
|
|
24
|
+
}
|
|
25
|
+
}, 1500);
|
|
26
|
+
}
|
|
27
|
+
}).catch((err) => {
|
|
28
|
+
console.error("Failed to copy code to clipboard:", err);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
widgetRoot.appendChild(this.button);
|
|
32
|
+
return widgetRoot;
|
|
33
|
+
}
|
|
34
|
+
eq(other) {
|
|
35
|
+
return other.lang === this.lang && other.codeContent === this.codeContent;
|
|
36
|
+
}
|
|
37
|
+
ignoreEvent(event) {
|
|
38
|
+
if ((event.type === "mousedown" || event.type === "click") && this.button && this.button.contains(event.target)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class EndFenceWidget extends WidgetType {
|
|
45
|
+
toDOM(view) {
|
|
46
|
+
const div = document.createElement("div");
|
|
47
|
+
div.className = "hidden";
|
|
48
|
+
return div;
|
|
49
|
+
}
|
|
50
|
+
ignoreEvent() {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type EditorView, WidgetType } from "@codemirror/view";
|
|
2
|
+
export declare class InlineLatexWidget extends WidgetType {
|
|
3
|
+
private readonly content;
|
|
4
|
+
constructor(content: string);
|
|
5
|
+
toDOM(view: EditorView): HTMLElement;
|
|
6
|
+
}
|
|
7
|
+
export declare class BlockLatexWidget extends WidgetType {
|
|
8
|
+
private readonly content;
|
|
9
|
+
constructor(content: string);
|
|
10
|
+
toDOM(view: EditorView): HTMLElement;
|
|
11
|
+
}
|