-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathcode-block.ts
More file actions
117 lines (94 loc) · 3.72 KB
/
code-block.ts
File metadata and controls
117 lines (94 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { Node as ProsemirrorNode } from "prosemirror-model";
import { EditorView, NodeView } from "prosemirror-view";
import {
getBlockLanguage,
getLoadedLanguages,
} from "../../shared/highlighting/highlight-plugin";
import { escapeHTML } from "../../shared/utils";
type getPosParam = boolean | (() => number);
/**
* View with <code> wrapping/decorations for code_block nodes
*/
export class CodeBlockView implements NodeView {
dom?: HTMLElement | null;
contentDOM?: HTMLElement | null;
private language: ReturnType<CodeBlockView["getLanguageFromBlock"]> = null;
constructor(node: ProsemirrorNode, view: EditorView, getPos: getPosParam) {
this.dom = document.createElement("div");
this.dom.classList.add("ps-relative", "p0", "ws-normal", "ow-normal");
const rawLanguage = this.getLanguageFromBlock(node);
this.language = rawLanguage;
this.dom.innerHTML = escapeHTML`
<pre class="s-code-block"><code class="content-dom"></code></pre>
<div class="s-select s-select__sm ps-absolute t6 r6"><select class="js-lang-select"></select></div>
`;
this.contentDOM = this.dom.querySelector(".content-dom");
this.initializeLanguageSelect(view, getPos);
this.updateDisplayedLanguage();
}
update(node: ProsemirrorNode): boolean {
// don't allow the node to be changed into anything other than a code_block
if (node.type.name !== "code_block") {
return false;
}
const rawLanguage = this.getLanguageFromBlock(node);
if (this.language.raw !== rawLanguage.raw) {
this.language = rawLanguage;
this.updateDisplayedLanguage();
}
return true;
}
private initializeLanguageSelect(view: EditorView, getPos: getPosParam) {
const $sel =
this.dom.querySelector<HTMLSelectElement>(".js-lang-select");
// add an "auto" dropdown that we can target via JS
const autoOpt = document.createElement("option");
autoOpt.textContent = "auto";
autoOpt.value = "auto";
autoOpt.className = "js-auto-option";
$sel.appendChild(autoOpt);
getLoadedLanguages().forEach((lang) => {
const opt = document.createElement("option");
opt.value = lang;
opt.textContent = lang;
opt.defaultSelected = lang === this.language.raw;
$sel.appendChild(opt);
});
if (typeof getPos !== "function") {
return;
}
// when the dropdown is changed, update the language on the node
$sel.addEventListener("change", (e) => {
e.stopPropagation();
const newLang = $sel.value;
view.dispatch(
view.state.tr.setNodeMarkup(getPos(), null, {
params: newLang === "auto" ? null : newLang,
detectedHighlightLanguage: null,
})
);
});
}
private updateDisplayedLanguage() {
const lang = this.language.raw;
const $sel =
this.dom.querySelector<HTMLSelectElement>(".js-lang-select");
const $auto = $sel.querySelector(".js-auto-option");
if (this.language.autodetected) {
$sel.value = "auto";
// TODO localization
$auto.textContent = lang + " (auto)";
} else {
$sel.value = lang;
$auto.textContent = "auto";
}
}
private getLanguageFromBlock(node: ProsemirrorNode) {
const autodetectedLanguage = node.attrs
.detectedHighlightLanguage as string;
return {
raw: autodetectedLanguage || getBlockLanguage(node, "auto"),
autodetected: !!autodetectedLanguage,
};
}
}