Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add syntax highlight using highlight.js for codeblocks #4820

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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