Skip to content

Commit e2d92fc

Browse files
committed
Add syntax highlight using highlight.js for codeblocks
1 parent 82a437f commit e2d92fc

File tree

7 files changed

+3985
-9
lines changed

7 files changed

+3985
-9
lines changed

libraries/highlight_js/highlight.min.js

Lines changed: 3861 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2+
Theme: StackOverflow Dark
3+
Description: Dark theme as used on stackoverflow.com
4+
Author: stackoverflow.com
5+
Maintainer: @Hirse
6+
Website: https://github.com/StackExchange/Stacks
7+
License: MIT
8+
Updated: 2021-05-15
9+
10+
Updated for @stackoverflow/stacks v0.64.0
11+
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
12+
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
13+
*/.hljs{color:#fff;background:#1c1b1b}.hljs-subst{color:#fff}.hljs-comment{color:#999}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#88aece}.hljs-attribute{color:#c59bc1}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#f08d49}.hljs-selector-class{color:#88aece}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#b5bd68}.hljs-meta,.hljs-selector-pseudo{color:#88aece}.hljs-built_in,.hljs-literal,.hljs-title{color:#f08d49}.hljs-bullet,.hljs-code{color:#ccc}.hljs-meta .hljs-string{color:#b5bd68}.hljs-deletion{color:#de7176}.hljs-addition{color:#76c490}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2+
Theme: StackOverflow Light
3+
Description: Light theme as used on stackoverflow.com
4+
Author: stackoverflow.com
5+
Maintainer: @Hirse
6+
Website: https://github.com/StackExchange/Stacks
7+
License: MIT
8+
Updated: 2021-05-15
9+
10+
Updated for @stackoverflow/stacks v0.64.0
11+
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
12+
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
13+
*/.hljs{color:#2f3337;background:#f6f6f6}.hljs-subst{color:#2f3337}.hljs-comment{color:#656e77}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#015692}.hljs-attribute{color:#803378}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#b75501}.hljs-selector-class{color:#015692}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#54790d}.hljs-meta,.hljs-selector-pseudo{color:#015692}.hljs-built_in,.hljs-literal,.hljs-title{color:#b75501}.hljs-bullet,.hljs-code{color:#535a60}.hljs-meta .hljs-string{color:#54790d}.hljs-deletion{color:#c02d2e}.hljs-addition{color:#2f6f44}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const highlight_codeblock = (editor, codeblock_node)=>{
2+
editor.model.change((writer)=> {
3+
let selection_pos = editor.model.document.selection.getLastPosition();
4+
const language = codeblock_node.getAttribute('language');
5+
let childs = [];
6+
codeblock_node.getChildren().forEach(node => childs.push(node));
7+
writer.remove(writer.createRangeIn(codeblock_node));
8+
childs.forEach(node =>{
9+
if(node.is("text") && !node.getAttribute("hljs-class")){
10+
let highlightedHtml = hljs.highlight(node.data, { language: language }).value;
11+
let dom = new DOMParser().parseFromString('<div>' + highlightedHtml + '</div>', "text/xml");
12+
for (let child_node of dom.children[0].childNodes) {
13+
// This is to avoid some weird issue when pasting multi-line string into code block.
14+
// The linebreakers would be converted to softbreak (<br>) in code blocks.
15+
// When editing, it works well/
16+
// when pasting, the <br> would be inserted but the original linebreaker would not be removed, result in extra empty lines.
17+
// This may due to some event listener order issues.
18+
// To solve, manully trim linebreakers.
19+
//
20+
21+
let text_content = child_node.textContent;
22+
if (text_content.endsWith("\r") || text_content.endsWith("\n")){
23+
text_content = text_content.substring(0, text_content.length - 1);
24+
// the position need to decrement... otherwise it would be invalid due to the trimming.
25+
selection_pos.path[1]-=1;
26+
}
27+
if (child_node.nodeName === 'span') {
28+
writer.appendText(text_content, {'hljs-class': child_node.className}, codeblock_node)
29+
} else if (child_node.nodeName === '#text') {
30+
writer.appendText(text_content, codeblock_node)
31+
}
32+
}
33+
}else{
34+
writer.append(node, codeblock_node);
35+
}
36+
});
37+
writer.setSelection(selection_pos);
38+
})
39+
};
40+
41+
export function add_codeblock_highlight(editor){
42+
editor.model.schema.extend('$text', {
43+
allowAttributes: [ 'hljs-class' ]
44+
});
45+
46+
editor.conversion.for('downcast')
47+
.attributeToElement( {
48+
model: {
49+
name: '$text',
50+
key: 'hljs-class'
51+
},
52+
view: ( modelAttr, { writer } ) => {
53+
return writer.createAttributeElement(
54+
'span', {class: modelAttr, 'span-type': "hljs"}
55+
);
56+
}
57+
});
58+
59+
editor.model.document.on('change:data', () => {
60+
let parent_node = editor.model.document.selection.getLastPosition().parent;
61+
if (parent_node.name==="codeBlock") {
62+
highlight_codeblock(editor, parent_node);
63+
}
64+
});
65+
};
66+
67+
export function force_highlight_codeblocks(editor) {
68+
editor.model.document.getRoots().forEach(root=>{
69+
root.getChildren().forEach(node=>{
70+
console.log(node);
71+
if (node.name==="codeBlock") {
72+
highlight_codeblock(editor, node);
73+
}
74+
})
75+
});
76+
}
77+

src/public/app/services/library_loader.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
2+
const HIGHLIGHT_JS = {
3+
js: ["libraries/highlight_js/highlight.min.js"],
4+
css: ["libraries/highlight_js/styles/stackoverflow-dark.min.css"]
5+
};
26

37
const CODE_MIRROR = {
48
js: [
@@ -119,6 +123,7 @@ export default {
119123
requireCss,
120124
requireLibrary,
121125
CKEDITOR,
126+
HIGHLIGHT_JS,
122127
CODE_MIRROR,
123128
ESLINT,
124129
RELATION_MAP,

src/public/app/services/mime_types.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import options from "./options.js";
22

33
const MIME_TYPES_DICT = [
4-
{ default: true, title: "Plain text", mime: "text/plain" },
4+
{ default: true, title: "Plain text", name: "Plaintext", mime: "text/plain" },
55
{ title: "APL", mime: "text/apl" },
66
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
77
{ title: "ASP.NET", mime: "application/x-aspx" },
@@ -59,8 +59,8 @@ const MIME_TYPES_DICT = [
5959
{ default: true, title: "Java", mime: "text/x-java" },
6060
{ title: "Java Server Pages", mime: "application/x-jsp" },
6161
{ title: "Jinja2", mime: "text/jinja2" },
62-
{ default: true, title: "JS backend", mime: "application/javascript;env=backend" },
63-
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend" },
62+
{ default: true, title: "JS backend", name: "Javascript", mime: "application/javascript;env=backend" },
63+
{ default: true, title: "JS frontend", name: "Javascript", mime: "application/javascript;env=frontend" },
6464
{ default: true, title: "JSON", mime: "application/json" },
6565
{ title: "JSON-LD", mime: "application/ld+json" },
6666
{ title: "JSX", mime: "text/jsx" },

src/public/app/widgets/type_widgets/editable_text.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
99
import link from "../../services/link.js";
1010
import appContext from "../../components/app_context.js";
1111
import dialogService from "../../services/dialog.js";
12+
import { force_highlight as force_highlight_codeblocks, add_codeblock_highlight} from "../../services/codeblock_highlight.js";
1213

1314
const ENABLE_INSPECTOR = false;
1415

@@ -104,12 +105,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
104105

105106
async initEditor() {
106107
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
108+
await libraryLoader.requireLibrary(libraryLoader.HIGHLIGHT_JS);
107109

108110
const codeBlockLanguages =
109111
(await mimeTypesService.getMimeTypes())
110-
.filter(mt => mt.enabled)
112+
.filter(mt => mt.enabled && hljs.getLanguage(mt.name ?? mt.title)!==undefined)
111113
.map(mt => ({
112-
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
114+
language: mt.name ?? mt.title,
113115
label: mt.title
114116
}));
115117

@@ -156,8 +158,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
156158
this.watchdog.setCreator(async (elementOrData, editorConfig) => {
157159
const editor = await BalloonEditor.create(elementOrData, editorConfig);
158160

159-
editor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
160-
161+
// syntax highlight logic
162+
add_codeblock_highlight(editor);
163+
editor.model.document.on('change:data', () => {
164+
this.spacedUpdate.scheduleUpdate();
165+
});
161166
if (glob.isDev && ENABLE_INSPECTOR) {
162167
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js');
163168
CKEditorInspector.attach(editor);
@@ -185,8 +190,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
185190
async doRefresh(note) {
186191
const blob = await note.getBlob();
187192

188-
await this.spacedUpdate.allowUpdateWithoutChange(() =>
189-
this.watchdog.editor.setData(blob.content || ""));
193+
await this.spacedUpdate.allowUpdateWithoutChange(() =>{
194+
this.watchdog.editor.setData(blob.content || "");
195+
force_highlight_codeblocks(this.watchdog.editor);
196+
});
190197
}
191198

192199
getData() {

0 commit comments

Comments
 (0)