Skip to content

Commit

Permalink
Add syntax highlight using highlight.js for codeblocks
Browse files Browse the repository at this point in the history
  • Loading branch information
J-141 committed Jul 23, 2024
1 parent 82a437f commit 04b30fe
Show file tree
Hide file tree
Showing 7 changed files with 3,984 additions and 9 deletions.
3,861 changes: 3,861 additions & 0 deletions libraries/highlight_js/highlight.min.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions libraries/highlight_js/styles/stackoverflow-dark.min.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: StackOverflow Dark
Description: Dark theme as used on stackoverflow.com
Author: stackoverflow.com
Maintainer: @Hirse
Website: https://github.com/StackExchange/Stacks
License: MIT
Updated: 2021-05-15
Updated for @stackoverflow/stacks v0.64.0
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
*/.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}
13 changes: 13 additions & 0 deletions libraries/highlight_js/styles/stackoverflow-light.min.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: StackOverflow Light
Description: Light theme as used on stackoverflow.com
Author: stackoverflow.com
Maintainer: @Hirse
Website: https://github.com/StackExchange/Stacks
License: MIT
Updated: 2021-05-15
Updated for @stackoverflow/stacks v0.64.0
Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less
Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less
*/.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}
76 changes: 76 additions & 0 deletions src/public/app/services/codeblock_highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const highlight_codeblock = (editor, codeblock_node)=>{
editor.model.change((writer)=> {
let selection_pos = editor.model.document.selection.getLastPosition();
const language = codeblock_node.getAttribute('language');
let childs = [];
codeblock_node.getChildren().forEach(node => childs.push(node));
writer.remove(writer.createRangeIn(codeblock_node));
childs.forEach(node =>{
if(node.is("text") && !node.getAttribute("hljs-class")){
let highlightedHtml = hljs.highlight(node.data, { language: language }).value;
let dom = new DOMParser().parseFromString('<div>' + highlightedHtml + '</div>', "text/xml");
for (let child_node of dom.children[0].childNodes) {
// This is to avoid some weird issue when pasting multi-line string into code block.
// The linebreakers would be converted to softbreak (<br>) in code blocks.
// When editing, it works well/
// when pasting, the <br> would be inserted but the original linebreaker would not be removed, result in extra empty lines.
// This may due to some event listener order issues.
// To solve, manully trim linebreakers.
//

let text_content = child_node.textContent;
if (text_content.endsWith("\r") || text_content.endsWith("\n")){
text_content = text_content.substring(0, text_content.length - 1);
// the position need to decrement... otherwise it would be invalid due to the trimming.
selection_pos.path[1]-=1;
}
if (child_node.nodeName === 'span') {
writer.appendText(text_content, {'hljs-class': child_node.className}, codeblock_node)
} else if (child_node.nodeName === '#text') {
writer.appendText(text_content, codeblock_node)
}
}
}else{
writer.append(node, codeblock_node);
}
});
writer.setSelection(selection_pos);
})
};

export function add_codeblock_highlight(editor){
editor.model.schema.extend('$text', {
allowAttributes: [ 'hljs-class' ]
});

editor.conversion.for('downcast')
.attributeToElement( {
model: {
name: '$text',
key: 'hljs-class'
},
view: ( modelAttr, { writer } ) => {
return writer.createAttributeElement(
'span', {class: modelAttr, 'span-type': "hljs"}
);
}
});

editor.model.document.on('change:data', () => {
let parent_node = editor.model.document.selection.getLastPosition().parent;
if (parent_node.name==="codeBlock") {
highlight_codeblock(editor, parent_node);
}
});
};

export function force_highlight_codeblocks(editor) {
editor.model.document.getRoots().forEach(root=>{
root.getChildren().forEach(node=>{
if (node.name==="codeBlock") {
highlight_codeblock(editor, node);
}
})
});
}

5 changes: 5 additions & 0 deletions src/public/app/services/library_loader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
const HIGHLIGHT_JS = {
js: ["libraries/highlight_js/highlight.min.js"],
css: ["libraries/highlight_js/styles/stackoverflow-dark.min.css"]
};

const CODE_MIRROR = {
js: [
Expand Down Expand Up @@ -119,6 +123,7 @@ export default {
requireCss,
requireLibrary,
CKEDITOR,
HIGHLIGHT_JS,
CODE_MIRROR,
ESLINT,
RELATION_MAP,
Expand Down
6 changes: 3 additions & 3 deletions src/public/app/services/mime_types.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import options from "./options.js";

const MIME_TYPES_DICT = [
{ default: true, title: "Plain text", mime: "text/plain" },
{ default: true, title: "Plain text", name: "Plaintext", mime: "text/plain" },
{ title: "APL", mime: "text/apl" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
{ title: "ASP.NET", mime: "application/x-aspx" },
Expand Down Expand Up @@ -59,8 +59,8 @@ const MIME_TYPES_DICT = [
{ default: true, title: "Java", mime: "text/x-java" },
{ title: "Java Server Pages", mime: "application/x-jsp" },
{ title: "Jinja2", mime: "text/jinja2" },
{ default: true, title: "JS backend", mime: "application/javascript;env=backend" },
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend" },
{ default: true, title: "JS backend", name: "Javascript", mime: "application/javascript;env=backend" },
{ default: true, title: "JS frontend", name: "Javascript", mime: "application/javascript;env=frontend" },
{ default: true, title: "JSON", mime: "application/json" },
{ title: "JSON-LD", mime: "application/ld+json" },
{ title: "JSX", mime: "text/jsx" },
Expand Down
19 changes: 13 additions & 6 deletions src/public/app/widgets/type_widgets/editable_text.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import link from "../../services/link.js";
import appContext from "../../components/app_context.js";
import dialogService from "../../services/dialog.js";
import { force_highlight_codeblocks, add_codeblock_highlight} from "../../services/codeblock_highlight.js";

const ENABLE_INSPECTOR = false;

Expand Down Expand Up @@ -104,12 +105,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {

async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
await libraryLoader.requireLibrary(libraryLoader.HIGHLIGHT_JS);

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

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

editor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());

// syntax highlight logic
add_codeblock_highlight(editor);
editor.model.document.on('change:data', () => {
this.spacedUpdate.scheduleUpdate();
});
if (glob.isDev && ENABLE_INSPECTOR) {
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(editor);
Expand Down Expand Up @@ -185,8 +190,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
async doRefresh(note) {
const blob = await note.getBlob();

await this.spacedUpdate.allowUpdateWithoutChange(() =>
this.watchdog.editor.setData(blob.content || ""));
await this.spacedUpdate.allowUpdateWithoutChange(() =>{
this.watchdog.editor.setData(blob.content || "");
force_highlight_codeblocks(this.watchdog.editor);
});
}

getData() {
Expand Down

0 comments on commit 04b30fe

Please sign in to comment.