leepi 0.0.2 → 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/dist/core/active-marks.d.ts +15 -0
- package/dist/core/active-marks.js +57 -0
- package/dist/core/commands.d.ts +39 -0
- package/dist/core/commands.js +415 -0
- package/dist/core/editor.d.ts +25 -0
- package/dist/core/editor.js +102 -0
- package/dist/core/field-notifier.d.ts +21 -0
- package/dist/core/field-notifier.js +56 -0
- package/dist/core/highlight-style.js +60 -0
- package/dist/core/highlight.d.ts +8 -0
- package/dist/core/highlight.js +34 -0
- package/dist/core/plugins/blockquote.d.ts +11 -0
- package/dist/core/plugins/blockquote.js +78 -0
- package/dist/core/plugins/bracket.d.ts +6 -0
- package/dist/core/plugins/bracket.js +38 -0
- package/dist/core/plugins/code-block.d.ts +27 -0
- package/dist/core/plugins/code-block.js +207 -0
- package/dist/core/plugins/heading.d.ts +13 -0
- package/dist/core/plugins/heading.js +111 -0
- package/dist/core/plugins/inline.d.ts +14 -0
- package/dist/core/plugins/inline.js +103 -0
- package/dist/core/plugins/link.d.ts +25 -0
- package/dist/core/plugins/link.js +104 -0
- package/dist/core/plugins/list.d.ts +14 -0
- package/dist/core/plugins/list.js +91 -0
- package/dist/core/plugins/table.d.ts +12 -0
- package/dist/core/plugins/table.js +161 -0
- package/dist/core/plugins.d.ts +9 -0
- package/dist/core/plugins.js +9 -0
- package/dist/core/popover.d.ts +9 -0
- package/dist/core/popover.js +16 -0
- package/dist/core/registry.d.ts +10 -0
- package/dist/core/registry.js +8 -0
- package/dist/core/types.d.ts +25 -0
- package/dist/core/types.js +0 -0
- package/dist/core/utils.d.ts +13 -0
- package/dist/core/utils.js +32 -0
- package/dist/leepi.css +461 -0
- package/dist/react/code-block-popover.d.ts +76 -0
- package/dist/react/code-block-popover.js +223 -0
- package/dist/react/context.d.ts +42 -0
- package/dist/react/context.js +88 -0
- package/dist/react/editor.d.ts +30 -0
- package/dist/react/editor.js +60 -0
- package/dist/react/floating-toolbar.d.ts +30 -0
- package/dist/react/floating-toolbar.js +87 -0
- package/dist/react/link-popover.d.ts +70 -0
- package/dist/react/link-popover.js +222 -0
- package/dist/react/preview.d.ts +13 -0
- package/dist/react/preview.js +56 -0
- package/dist/react/toolbar.d.ts +51 -0
- package/dist/react/toolbar.js +161 -0
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ViewPlugin } from "@codemirror/view";
|
|
2
|
+
//#region src/core/field-notifier.ts
|
|
3
|
+
const fieldListeners = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
const fieldSnapshots = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe to changes of a StateField on a given EditorView.
|
|
7
|
+
* Returns an unsubscribe function.
|
|
8
|
+
*/
|
|
9
|
+
function subscribeToField(view, field, cb) {
|
|
10
|
+
let viewMap = fieldListeners.get(view);
|
|
11
|
+
if (!viewMap) {
|
|
12
|
+
viewMap = /* @__PURE__ */ new Map();
|
|
13
|
+
fieldListeners.set(view, viewMap);
|
|
14
|
+
}
|
|
15
|
+
let listeners = viewMap.get(field);
|
|
16
|
+
if (!listeners) {
|
|
17
|
+
listeners = /* @__PURE__ */ new Set();
|
|
18
|
+
viewMap.set(field, listeners);
|
|
19
|
+
}
|
|
20
|
+
listeners.add(cb);
|
|
21
|
+
return () => {
|
|
22
|
+
listeners.delete(cb);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Get the current snapshot of a StateField for useSyncExternalStore */
|
|
26
|
+
function getFieldSnapshot(view, field) {
|
|
27
|
+
return fieldSnapshots.get(view)?.get(field);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ViewPlugin that watches all StateFields with subscribers and notifies
|
|
31
|
+
* when their values change (by reference).
|
|
32
|
+
*/
|
|
33
|
+
const fieldNotifierPlugin = ViewPlugin.fromClass(class {
|
|
34
|
+
update(update) {
|
|
35
|
+
const viewMap = fieldListeners.get(update.view);
|
|
36
|
+
if (!viewMap) return;
|
|
37
|
+
let snapMap = fieldSnapshots.get(update.view);
|
|
38
|
+
if (!snapMap) {
|
|
39
|
+
snapMap = /* @__PURE__ */ new Map();
|
|
40
|
+
fieldSnapshots.set(update.view, snapMap);
|
|
41
|
+
}
|
|
42
|
+
for (const [field, listeners] of viewMap) {
|
|
43
|
+
const newVal = update.state.field(field);
|
|
44
|
+
if (newVal !== snapMap.get(field)) {
|
|
45
|
+
snapMap.set(field, newVal);
|
|
46
|
+
for (const cb of listeners) cb();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
/** Extension to include in createEditorState */
|
|
52
|
+
function fieldNotifier() {
|
|
53
|
+
return fieldNotifierPlugin;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { fieldNotifier, fieldNotifierPlugin, getFieldSnapshot, subscribeToField };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
3
|
+
import { tags } from "@lezer/highlight";
|
|
4
|
+
//#region src/core/highlight-style.ts
|
|
5
|
+
/** Shared highlight style for faded markdown syntax tokens (e.g. `#`, `>`, `-`, `` ` ``). */
|
|
6
|
+
const highlightStyle = HighlightStyle.define([
|
|
7
|
+
{
|
|
8
|
+
tag: tags.processingInstruction,
|
|
9
|
+
color: "var(--lp-color-text-faded)",
|
|
10
|
+
opacity: "0.4",
|
|
11
|
+
fontFamily: "var(--lp-font-mono)"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
tag: tags.meta,
|
|
15
|
+
color: "var(--lp-color-text-faded)",
|
|
16
|
+
opacity: "0.4",
|
|
17
|
+
fontFamily: "var(--lp-font-mono)"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
tag: tags.labelName,
|
|
21
|
+
color: "var(--lp-color-text-faded)",
|
|
22
|
+
opacity: "0.4",
|
|
23
|
+
fontFamily: "var(--lp-font-mono)"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
tag: tags.contentSeparator,
|
|
27
|
+
color: "var(--lp-color-text-faded)",
|
|
28
|
+
opacity: "0.4",
|
|
29
|
+
fontFamily: "var(--lp-font-mono)"
|
|
30
|
+
}
|
|
31
|
+
]);
|
|
32
|
+
const editorTheme = EditorView.theme({
|
|
33
|
+
"&": {
|
|
34
|
+
height: "100%",
|
|
35
|
+
flex: "1",
|
|
36
|
+
fontSize: "var(--lp-font-size, 0.875rem)",
|
|
37
|
+
fontFamily: "var(--lp-font-body, system-ui, -apple-system, sans-serif)"
|
|
38
|
+
},
|
|
39
|
+
".cm-scroller": {
|
|
40
|
+
overflow: "auto",
|
|
41
|
+
fontFamily: "inherit",
|
|
42
|
+
lineHeight: "var(--lp-line-height, 1.6)"
|
|
43
|
+
},
|
|
44
|
+
".cm-content": {
|
|
45
|
+
maxWidth: "var(--lp-content-max-width, 48rem)",
|
|
46
|
+
margin: "0 auto",
|
|
47
|
+
padding: "1.5rem var(--lp-content-padding, 2rem)",
|
|
48
|
+
caretColor: "var(--lp-color-caret, currentColor)"
|
|
49
|
+
},
|
|
50
|
+
"&.cm-focused": { outline: "none" },
|
|
51
|
+
".cm-activeLine": { backgroundColor: "transparent" },
|
|
52
|
+
".cm-gutters": { display: "none" },
|
|
53
|
+
".cm-has-deco span[class]": {
|
|
54
|
+
backgroundColor: "transparent",
|
|
55
|
+
padding: "0",
|
|
56
|
+
borderRadius: "0"
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
//#endregion
|
|
60
|
+
export { editorTheme, highlightStyle };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/core/highlight.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Highlight code using Lezer parsers (same ones CodeMirror uses).
|
|
4
|
+
* Returns HTML string with `tok-*` classes for styling.
|
|
5
|
+
*/
|
|
6
|
+
declare function highlightCode(code: string, lang: string): Promise<string>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { highlightCode };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { LanguageDescription } from "@codemirror/language";
|
|
2
|
+
import { classHighlighter, highlightTree } from "@lezer/highlight";
|
|
3
|
+
//#region src/core/highlight.ts
|
|
4
|
+
let languageList = null;
|
|
5
|
+
async function getLanguages() {
|
|
6
|
+
if (languageList) return languageList;
|
|
7
|
+
const { languages } = await import("@codemirror/language-data");
|
|
8
|
+
languageList = languages;
|
|
9
|
+
return languages;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Highlight code using Lezer parsers (same ones CodeMirror uses).
|
|
13
|
+
* Returns HTML string with `tok-*` classes for styling.
|
|
14
|
+
*/
|
|
15
|
+
async function highlightCode(code, lang) {
|
|
16
|
+
const languages = await getLanguages();
|
|
17
|
+
const desc = LanguageDescription.matchLanguageName(languages, lang, true);
|
|
18
|
+
if (!desc) return escapeHtml(code);
|
|
19
|
+
const tree = (await desc.load()).language.parser.parse(code);
|
|
20
|
+
let pos = 0;
|
|
21
|
+
let result = "";
|
|
22
|
+
highlightTree(tree, classHighlighter, (from, to, classes) => {
|
|
23
|
+
if (from > pos) result += escapeHtml(code.slice(pos, from));
|
|
24
|
+
result += `<span class="${classes}">${escapeHtml(code.slice(from, to))}</span>`;
|
|
25
|
+
pos = to;
|
|
26
|
+
});
|
|
27
|
+
if (pos < code.length) result += escapeHtml(code.slice(pos));
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function escapeHtml(text) {
|
|
31
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { highlightCode };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Extension } from "@codemirror/state";
|
|
2
|
+
|
|
3
|
+
//#region src/core/plugins/blockquote.d.ts
|
|
4
|
+
interface BlockquotePluginOptions {
|
|
5
|
+
shortcuts?: {
|
|
6
|
+
blockquote?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare function blockquotePlugin(options?: BlockquotePluginOptions): Extension;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { BlockquotePluginOptions, blockquotePlugin };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { markRegistry, shortcutRegistry } from "../registry.js";
|
|
2
|
+
import { toggleBlockquote } from "../commands.js";
|
|
3
|
+
import { Decoration, EditorView, ViewPlugin, keymap } from "@codemirror/view";
|
|
4
|
+
import { HighlightStyle, syntaxHighlighting, syntaxTree } from "@codemirror/language";
|
|
5
|
+
import { RangeSetBuilder } from "@codemirror/state";
|
|
6
|
+
import { tags } from "@lezer/highlight";
|
|
7
|
+
//#region src/core/plugins/blockquote.ts
|
|
8
|
+
const defaultShortcuts = { blockquote: "Mod-Shift-b" };
|
|
9
|
+
const blockquoteDecoration = Decoration.line({ class: "cm-blockquote-line" });
|
|
10
|
+
function buildBlockquoteDecorations(state) {
|
|
11
|
+
const builder = new RangeSetBuilder();
|
|
12
|
+
const tree = syntaxTree(state);
|
|
13
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14
|
+
tree.iterate({ enter(node) {
|
|
15
|
+
if (node.name === "Blockquote") {
|
|
16
|
+
const from = state.doc.lineAt(node.from).number;
|
|
17
|
+
const to = state.doc.lineAt(node.to).number;
|
|
18
|
+
for (let i = from; i <= to; i++) if (!seen.has(i)) {
|
|
19
|
+
seen.add(i);
|
|
20
|
+
const line = state.doc.line(i);
|
|
21
|
+
builder.add(line.from, line.from, blockquoteDecoration);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} });
|
|
25
|
+
return builder.finish();
|
|
26
|
+
}
|
|
27
|
+
const blockquoteLineDecorations = ViewPlugin.fromClass(class {
|
|
28
|
+
decorations;
|
|
29
|
+
constructor(view) {
|
|
30
|
+
this.decorations = buildBlockquoteDecorations(view.state);
|
|
31
|
+
}
|
|
32
|
+
update(update) {
|
|
33
|
+
if (update.docChanged || update.viewportChanged || syntaxTree(update.state) !== syntaxTree(update.startState)) this.decorations = buildBlockquoteDecorations(update.state);
|
|
34
|
+
}
|
|
35
|
+
}, { decorations: (v) => v.decorations });
|
|
36
|
+
const blockquoteTheme = EditorView.theme({
|
|
37
|
+
".cm-blockquote-line": { position: "relative" },
|
|
38
|
+
".cm-blockquote-line::before": {
|
|
39
|
+
content: "\"\"",
|
|
40
|
+
position: "absolute",
|
|
41
|
+
right: "calc(100% + 0.5ch)",
|
|
42
|
+
top: "0",
|
|
43
|
+
bottom: "0",
|
|
44
|
+
width: "3px",
|
|
45
|
+
backgroundColor: "var(--lp-color-border, rgba(0, 0, 0, 0.12))",
|
|
46
|
+
pointerEvents: "none"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const blockquoteHighlight = HighlightStyle.define([{
|
|
50
|
+
tag: tags.quote,
|
|
51
|
+
color: "var(--lp-color-text-secondary)"
|
|
52
|
+
}]);
|
|
53
|
+
function blockquotePlugin(options) {
|
|
54
|
+
const keys = {
|
|
55
|
+
...defaultShortcuts,
|
|
56
|
+
...options?.shortcuts
|
|
57
|
+
};
|
|
58
|
+
return [
|
|
59
|
+
syntaxHighlighting(blockquoteHighlight),
|
|
60
|
+
shortcutRegistry.of([{
|
|
61
|
+
key: keys.blockquote,
|
|
62
|
+
action: "blockquote",
|
|
63
|
+
plugin: "blockquote"
|
|
64
|
+
}]),
|
|
65
|
+
markRegistry.of([{
|
|
66
|
+
mark: "blockquote",
|
|
67
|
+
detect: (nodeName) => nodeName === "Blockquote"
|
|
68
|
+
}]),
|
|
69
|
+
keymap.of([{
|
|
70
|
+
key: keys.blockquote,
|
|
71
|
+
run: toggleBlockquote
|
|
72
|
+
}]),
|
|
73
|
+
blockquoteLineDecorations,
|
|
74
|
+
blockquoteTheme
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { blockquotePlugin };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
import { EditorSelection } from "@codemirror/state";
|
|
3
|
+
//#region src/core/plugins/bracket.ts
|
|
4
|
+
const BRACKETS = {
|
|
5
|
+
"(": ")",
|
|
6
|
+
"[": "]",
|
|
7
|
+
"{": "}"
|
|
8
|
+
};
|
|
9
|
+
function bracketPlugin() {
|
|
10
|
+
return EditorView.inputHandler.of((view, from, to, insert) => {
|
|
11
|
+
const close = BRACKETS[insert];
|
|
12
|
+
if (!close) return false;
|
|
13
|
+
const range = view.state.selection.main;
|
|
14
|
+
if (range.empty) {
|
|
15
|
+
view.dispatch({
|
|
16
|
+
changes: {
|
|
17
|
+
from,
|
|
18
|
+
to,
|
|
19
|
+
insert: insert + close
|
|
20
|
+
},
|
|
21
|
+
selection: EditorSelection.cursor(from + 1)
|
|
22
|
+
});
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const selected = view.state.sliceDoc(range.from, range.to);
|
|
26
|
+
view.dispatch({
|
|
27
|
+
changes: {
|
|
28
|
+
from: range.from,
|
|
29
|
+
to: range.to,
|
|
30
|
+
insert: insert + selected + close
|
|
31
|
+
},
|
|
32
|
+
selection: EditorSelection.range(range.from + 1, range.from + 1 + selected.length)
|
|
33
|
+
});
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { bracketPlugin };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PopoverRequest } from "../types.js";
|
|
2
|
+
import { applyCodeBlock, findCodeFenceAtCursor } from "../commands.js";
|
|
3
|
+
import { Extension } from "@codemirror/state";
|
|
4
|
+
|
|
5
|
+
//#region src/core/plugins/code-block.d.ts
|
|
6
|
+
interface CodeBlockData {
|
|
7
|
+
/** Pre-filled language */
|
|
8
|
+
lang: string;
|
|
9
|
+
/** Pre-filled filename */
|
|
10
|
+
filename: string;
|
|
11
|
+
/** Range of the opening fence line to replace */
|
|
12
|
+
fenceFrom: number;
|
|
13
|
+
fenceTo: number;
|
|
14
|
+
/** Whether this is editing an existing block or inserting a new one */
|
|
15
|
+
isNew: boolean;
|
|
16
|
+
/** For new blocks: the cursor position where the block should be inserted */
|
|
17
|
+
insertPos?: number;
|
|
18
|
+
}
|
|
19
|
+
type CodeBlockRequest = PopoverRequest<"codeblock", CodeBlockData>;
|
|
20
|
+
interface CodeBlockPluginOptions {
|
|
21
|
+
shortcuts?: {
|
|
22
|
+
codeBlock?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
declare function codeBlockPlugin(options?: CodeBlockPluginOptions): Extension;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CodeBlockData, CodeBlockPluginOptions, CodeBlockRequest, codeBlockPlugin };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { markRegistry, shortcutRegistry } from "../registry.js";
|
|
2
|
+
import { findCodeFenceAtCursor } from "../commands.js";
|
|
3
|
+
import { openPopover } from "../popover.js";
|
|
4
|
+
import { Decoration, EditorView, ViewPlugin, keymap } from "@codemirror/view";
|
|
5
|
+
import { HighlightStyle, syntaxHighlighting, syntaxTree } from "@codemirror/language";
|
|
6
|
+
import { RangeSetBuilder } from "@codemirror/state";
|
|
7
|
+
import { tags } from "@lezer/highlight";
|
|
8
|
+
//#region src/core/plugins/code-block.ts
|
|
9
|
+
function buildCodeLineNumbers(state) {
|
|
10
|
+
const builder = new RangeSetBuilder();
|
|
11
|
+
const tree = syntaxTree(state);
|
|
12
|
+
const doc = state.doc;
|
|
13
|
+
tree.iterate({ enter(node) {
|
|
14
|
+
if (node.name !== "FencedCode") return;
|
|
15
|
+
const startLine = doc.lineAt(node.from);
|
|
16
|
+
const endLine = doc.lineAt(node.to);
|
|
17
|
+
const totalLines = endLine.number - startLine.number - 1;
|
|
18
|
+
const digits = String(totalLines).length;
|
|
19
|
+
let num = 1;
|
|
20
|
+
for (let l = startLine.number + 1; l < endLine.number; l++) {
|
|
21
|
+
const line = doc.line(l);
|
|
22
|
+
const attributes = { "data-line-num": String(num++) };
|
|
23
|
+
if (digits > 3) attributes.style = `--line-digits:${digits}`;
|
|
24
|
+
builder.add(line.from, line.from, Decoration.line({
|
|
25
|
+
class: "cm-has-deco",
|
|
26
|
+
attributes
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
} });
|
|
30
|
+
return builder.finish();
|
|
31
|
+
}
|
|
32
|
+
const codeBlockLineNumbers = ViewPlugin.fromClass(class {
|
|
33
|
+
decorations;
|
|
34
|
+
constructor(view) {
|
|
35
|
+
this.decorations = buildCodeLineNumbers(view.state);
|
|
36
|
+
}
|
|
37
|
+
update(update) {
|
|
38
|
+
if (update.docChanged || update.viewportChanged || syntaxTree(update.state) !== syntaxTree(update.startState)) this.decorations = buildCodeLineNumbers(update.state);
|
|
39
|
+
}
|
|
40
|
+
}, { decorations: (v) => v.decorations });
|
|
41
|
+
const codeBlockTheme = EditorView.theme({
|
|
42
|
+
".cm-has-deco": {
|
|
43
|
+
position: "relative",
|
|
44
|
+
fontSize: "0.875em",
|
|
45
|
+
fontFamily: "var(--lp-font-mono, ui-monospace, monospace)"
|
|
46
|
+
},
|
|
47
|
+
".cm-has-deco::before": {
|
|
48
|
+
content: "attr(data-line-num)",
|
|
49
|
+
position: "absolute",
|
|
50
|
+
right: "calc(100% + 0.5ch)",
|
|
51
|
+
width: "calc(var(--line-digits, 3) * 1ch)",
|
|
52
|
+
textAlign: "right",
|
|
53
|
+
color: "var(--lp-color-text-faded)",
|
|
54
|
+
opacity: "0.5",
|
|
55
|
+
borderRadius: "3px",
|
|
56
|
+
padding: "0 0.3ch",
|
|
57
|
+
userSelect: "none",
|
|
58
|
+
font: "inherit",
|
|
59
|
+
pointerEvents: "none"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const codeHighlight = HighlightStyle.define([
|
|
63
|
+
{
|
|
64
|
+
tag: tags.keyword,
|
|
65
|
+
color: "var(--lp-syntax-keyword)"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
tag: tags.operator,
|
|
69
|
+
color: "var(--lp-syntax-operator)"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
tag: tags.variableName,
|
|
73
|
+
color: "var(--lp-syntax-variable)"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
tag: tags.function(tags.variableName),
|
|
77
|
+
color: "var(--lp-syntax-function)"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
tag: tags.definition(tags.variableName),
|
|
81
|
+
color: "var(--lp-syntax-type)"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
tag: tags.typeName,
|
|
85
|
+
color: "var(--lp-syntax-type)"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
tag: tags.className,
|
|
89
|
+
color: "var(--lp-syntax-type)"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
tag: tags.string,
|
|
93
|
+
color: "var(--lp-syntax-string)"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
tag: tags.number,
|
|
97
|
+
color: "var(--lp-syntax-number)"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
tag: tags.bool,
|
|
101
|
+
color: "var(--lp-syntax-number)"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
tag: tags.null,
|
|
105
|
+
color: "var(--lp-syntax-number)"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
tag: tags.comment,
|
|
109
|
+
color: "var(--lp-syntax-comment)",
|
|
110
|
+
fontStyle: "italic"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
tag: tags.propertyName,
|
|
114
|
+
color: "var(--lp-syntax-property)"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
tag: tags.function(tags.propertyName),
|
|
118
|
+
color: "var(--lp-syntax-function)"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
tag: tags.definition(tags.propertyName),
|
|
122
|
+
color: "var(--lp-syntax-function)"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
tag: tags.tagName,
|
|
126
|
+
color: "var(--lp-syntax-tag)"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
tag: tags.attributeName,
|
|
130
|
+
color: "var(--lp-syntax-attribute)"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
tag: tags.attributeValue,
|
|
134
|
+
color: "var(--lp-syntax-string)"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
tag: tags.regexp,
|
|
138
|
+
color: "var(--lp-syntax-string)"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
tag: tags.escape,
|
|
142
|
+
color: "var(--lp-syntax-escape)"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
tag: tags.punctuation,
|
|
146
|
+
color: "var(--lp-syntax-punctuation)"
|
|
147
|
+
}
|
|
148
|
+
]);
|
|
149
|
+
const defaultShortcuts = { codeBlock: "Mod-Shift-e" };
|
|
150
|
+
function codeBlockPlugin(options) {
|
|
151
|
+
const keys = {
|
|
152
|
+
...defaultShortcuts,
|
|
153
|
+
...options?.shortcuts
|
|
154
|
+
};
|
|
155
|
+
return [
|
|
156
|
+
syntaxHighlighting(codeHighlight),
|
|
157
|
+
shortcutRegistry.of([{
|
|
158
|
+
key: keys.codeBlock,
|
|
159
|
+
action: "codeblock",
|
|
160
|
+
plugin: "code-block"
|
|
161
|
+
}]),
|
|
162
|
+
markRegistry.of([{
|
|
163
|
+
mark: "codeblock",
|
|
164
|
+
detect: (nodeName) => nodeName === "FencedCode"
|
|
165
|
+
}]),
|
|
166
|
+
keymap.of([{
|
|
167
|
+
key: keys.codeBlock,
|
|
168
|
+
run(view) {
|
|
169
|
+
const { state } = view;
|
|
170
|
+
const range = state.selection.main;
|
|
171
|
+
const coords = view.coordsAtPos(range.from);
|
|
172
|
+
if (!coords) return false;
|
|
173
|
+
const existing = findCodeFenceAtCursor(view);
|
|
174
|
+
const req = existing ? {
|
|
175
|
+
type: "codeblock",
|
|
176
|
+
x: coords.left,
|
|
177
|
+
y: coords.bottom,
|
|
178
|
+
data: {
|
|
179
|
+
lang: existing.lang,
|
|
180
|
+
filename: existing.filename,
|
|
181
|
+
fenceFrom: existing.fenceLine.from,
|
|
182
|
+
fenceTo: existing.fenceLine.to,
|
|
183
|
+
isNew: false
|
|
184
|
+
}
|
|
185
|
+
} : {
|
|
186
|
+
type: "codeblock",
|
|
187
|
+
x: coords.left,
|
|
188
|
+
y: coords.bottom,
|
|
189
|
+
data: {
|
|
190
|
+
lang: "",
|
|
191
|
+
filename: "",
|
|
192
|
+
fenceFrom: 0,
|
|
193
|
+
fenceTo: 0,
|
|
194
|
+
isNew: true,
|
|
195
|
+
insertPos: range.from
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
view.dispatch({ effects: openPopover.of(req) });
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}]),
|
|
202
|
+
codeBlockLineNumbers,
|
|
203
|
+
codeBlockTheme
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { codeBlockPlugin };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Extension } from "@codemirror/state";
|
|
2
|
+
|
|
3
|
+
//#region src/core/plugins/heading.d.ts
|
|
4
|
+
interface HeadingPluginOptions {
|
|
5
|
+
shortcuts?: {
|
|
6
|
+
heading1?: string;
|
|
7
|
+
heading2?: string;
|
|
8
|
+
heading3?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
declare function headingPlugin(options?: HeadingPluginOptions): Extension;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { HeadingPluginOptions, headingPlugin };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { markRegistry, shortcutRegistry } from "../registry.js";
|
|
2
|
+
import { toggleHeading } from "../commands.js";
|
|
3
|
+
import { keymap } from "@codemirror/view";
|
|
4
|
+
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
|
5
|
+
import { tags } from "@lezer/highlight";
|
|
6
|
+
//#region src/core/plugins/heading.ts
|
|
7
|
+
const defaultShortcuts = {
|
|
8
|
+
heading1: "Mod-Alt-1",
|
|
9
|
+
heading2: "Mod-Alt-2",
|
|
10
|
+
heading3: "Mod-Alt-3"
|
|
11
|
+
};
|
|
12
|
+
const headingHighlight = HighlightStyle.define([
|
|
13
|
+
{
|
|
14
|
+
tag: tags.heading1,
|
|
15
|
+
fontSize: "2em",
|
|
16
|
+
fontWeight: "var(--lp-font-weight-bold, 700)",
|
|
17
|
+
fontFamily: "var(--lp-font-display)"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
tag: tags.heading2,
|
|
21
|
+
fontSize: "1.6em",
|
|
22
|
+
fontWeight: "var(--lp-font-weight-bold, 700)",
|
|
23
|
+
fontFamily: "var(--lp-font-display)"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
tag: tags.heading3,
|
|
27
|
+
fontSize: "1.3em",
|
|
28
|
+
fontWeight: "var(--lp-font-weight-semibold, 600)",
|
|
29
|
+
fontFamily: "var(--lp-font-display)"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
tag: tags.heading4,
|
|
33
|
+
fontSize: "1.1em",
|
|
34
|
+
fontWeight: "var(--lp-font-weight-semibold, 600)",
|
|
35
|
+
fontFamily: "var(--lp-font-display)"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
tag: tags.heading5,
|
|
39
|
+
fontSize: "1.05em",
|
|
40
|
+
fontWeight: "var(--lp-font-weight-semibold, 600)",
|
|
41
|
+
fontFamily: "var(--lp-font-display)"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
tag: tags.heading6,
|
|
45
|
+
fontSize: "1em",
|
|
46
|
+
fontWeight: "var(--lp-font-weight-semibold, 600)",
|
|
47
|
+
fontFamily: "var(--lp-font-display)"
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
function headingPlugin(options) {
|
|
51
|
+
const keys = {
|
|
52
|
+
...defaultShortcuts,
|
|
53
|
+
...options?.shortcuts
|
|
54
|
+
};
|
|
55
|
+
const extensions = [syntaxHighlighting(headingHighlight), markRegistry.of([
|
|
56
|
+
{
|
|
57
|
+
mark: "heading1",
|
|
58
|
+
detect: (nodeName) => nodeName === "ATXHeading1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
mark: "heading2",
|
|
62
|
+
detect: (nodeName) => nodeName === "ATXHeading2"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
mark: "heading3",
|
|
66
|
+
detect: (nodeName) => nodeName === "ATXHeading3"
|
|
67
|
+
}
|
|
68
|
+
])];
|
|
69
|
+
const keybindings = [];
|
|
70
|
+
const shortcuts = [];
|
|
71
|
+
if (keys.heading1) {
|
|
72
|
+
keybindings.push({
|
|
73
|
+
key: keys.heading1,
|
|
74
|
+
run: toggleHeading(1)
|
|
75
|
+
});
|
|
76
|
+
shortcuts.push({
|
|
77
|
+
key: keys.heading1,
|
|
78
|
+
action: "heading1",
|
|
79
|
+
plugin: "heading"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (keys.heading2) {
|
|
83
|
+
keybindings.push({
|
|
84
|
+
key: keys.heading2,
|
|
85
|
+
run: toggleHeading(2)
|
|
86
|
+
});
|
|
87
|
+
shortcuts.push({
|
|
88
|
+
key: keys.heading2,
|
|
89
|
+
action: "heading2",
|
|
90
|
+
plugin: "heading"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (keys.heading3) {
|
|
94
|
+
keybindings.push({
|
|
95
|
+
key: keys.heading3,
|
|
96
|
+
run: toggleHeading(3)
|
|
97
|
+
});
|
|
98
|
+
shortcuts.push({
|
|
99
|
+
key: keys.heading3,
|
|
100
|
+
action: "heading3",
|
|
101
|
+
plugin: "heading"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (keybindings.length > 0) {
|
|
105
|
+
extensions.push(keymap.of(keybindings));
|
|
106
|
+
extensions.push(shortcutRegistry.of(shortcuts));
|
|
107
|
+
}
|
|
108
|
+
return extensions;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { headingPlugin };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Extension } from "@codemirror/state";
|
|
2
|
+
|
|
3
|
+
//#region src/core/plugins/inline.d.ts
|
|
4
|
+
interface InlinePluginOptions {
|
|
5
|
+
shortcuts?: {
|
|
6
|
+
bold?: string;
|
|
7
|
+
italic?: string;
|
|
8
|
+
strikethrough?: string;
|
|
9
|
+
code?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
declare function inlinePlugin(options?: InlinePluginOptions): Extension;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { InlinePluginOptions, inlinePlugin };
|