mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-25 04:32:31 +01:00
162 lines
4.6 KiB
JavaScript
162 lines
4.6 KiB
JavaScript
const { persistSelection } = require("../utils");
|
|
const { TAGNAME, state, newlineToBR } = require("./utils");
|
|
const languages = require("./languages");
|
|
const hljs = require("highlight.js/lib/core");
|
|
|
|
const LANGUAGE_SELECT_LABEL_SELECTOR =
|
|
".tox-pop__dialog span.tox-tbtn__select-label";
|
|
|
|
function maplanguagesToMenuItems() {
|
|
if (state.languages.length === languages.length) return;
|
|
|
|
languages.forEach((language) => {
|
|
state.languages.push({
|
|
type: "choiceitem",
|
|
text: language.name,
|
|
value: language,
|
|
});
|
|
});
|
|
}
|
|
|
|
function addCodeBlockToolbar(editor) {
|
|
maplanguagesToMenuItems();
|
|
setupChangeLanguageButton(editor);
|
|
|
|
editor.ui.registry.addContextToolbar("codeblock-selection", {
|
|
predicate: (node) => {
|
|
if (node.nodeName === TAGNAME) {
|
|
state.activeBlock = node;
|
|
const languageShortname = parseCodeblockLanguage(node);
|
|
const language = languages.find(
|
|
(l) => l.shortname === languageShortname
|
|
);
|
|
setupChangeLanguageButton(editor, language && language.name);
|
|
return true;
|
|
} else {
|
|
state.activeBlock = null;
|
|
}
|
|
return false;
|
|
},
|
|
items: "copy languages",
|
|
position: "node",
|
|
});
|
|
}
|
|
|
|
function setupChangeLanguageButton(editor, text = "Plain text") {
|
|
changeLanguageSelectLabel(text);
|
|
|
|
editor.ui.registry.addSplitButton("languages", {
|
|
text: text,
|
|
onAction: async () => {
|
|
const isPlaintext = text === "Plain text";
|
|
if (!isPlaintext) return;
|
|
const language = parseCodeblockLanguage(state.activeBlock);
|
|
if (!language) return;
|
|
selectLanguage(editor, language);
|
|
},
|
|
onItemAction: async (_, language) => {
|
|
if (!language) return;
|
|
await selectLanguage(editor, language);
|
|
},
|
|
select: (language) => language && language.name === text,
|
|
fetch: (callback) => callback(state.languages),
|
|
});
|
|
}
|
|
|
|
async function selectLanguage(editor, language) {
|
|
setupChangeLanguageButton(editor, "Loading");
|
|
await applyHighlighting(editor, language.shortname);
|
|
setupChangeLanguageButton(editor, language.name);
|
|
editor.focus();
|
|
}
|
|
|
|
function changeLanguageSelectLabel(text) {
|
|
const label = document.querySelector(LANGUAGE_SELECT_LABEL_SELECTOR);
|
|
if (!label || label.textContent === text) return;
|
|
label.textContent = text;
|
|
}
|
|
|
|
function parseCodeblockLanguage(node) {
|
|
if (!node || node.tagName !== TAGNAME) return;
|
|
|
|
const languageAliases = getLanguageFromClassList(node).split("-");
|
|
if (languageAliases.length <= 1) return;
|
|
return languageAliases[1];
|
|
}
|
|
|
|
async function applyHighlighting(editor, language) {
|
|
// load hljs into the editor window which can be the iframe
|
|
// or the main window. This is required so language definitions
|
|
// can be loaded.
|
|
editor.contentWindow.hljs = hljs;
|
|
|
|
if (!language) return;
|
|
if (!hljs.getLanguage(language)) await loadLanguage(editor, language);
|
|
|
|
const node = state.activeBlock;
|
|
|
|
persistSelection(node, () => {
|
|
const code = hljs.highlight(node.innerText, {
|
|
language,
|
|
});
|
|
node.innerHTML = newlineToBR(code.value);
|
|
});
|
|
changeCodeblockClassName(node, `language-${language}`);
|
|
|
|
editor.setDirty(false); // Force not dirty state
|
|
}
|
|
|
|
function changeCodeblockClassName(node, className) {
|
|
const language = getLanguageFromClassList(node);
|
|
if (language === className) return;
|
|
if (!!language) node.classList.replace(language, className);
|
|
else node.classList.add(className);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Element} node
|
|
*/
|
|
function getLanguageFromClassList(node) {
|
|
for (let className of node.classList.values()) {
|
|
if (className.startsWith("language") || className.startsWith("lang"))
|
|
return className;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
async function refreshHighlighting(editor) {
|
|
const language = parseCodeblockLanguage(state.activeBlock);
|
|
await applyHighlighting(editor, language);
|
|
}
|
|
|
|
const loadedLanguages = {};
|
|
async function loadLanguage(editor, shortName) {
|
|
if (loadedLanguages[shortName]) return hljs.getLanguage(shortName);
|
|
|
|
const url = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/languages/${shortName}.min.js`;
|
|
await loadScript(editor, url);
|
|
|
|
const lang = hljs.getLanguage(shortName);
|
|
loadedLanguages[shortName] = lang;
|
|
}
|
|
|
|
function loadScript(editor, url) {
|
|
return new Promise((resolve, reject) => {
|
|
const document = editor.dom.doc;
|
|
const script = document.createElement("script");
|
|
script.src = url;
|
|
// Append to the `head` element
|
|
document.head.appendChild(script);
|
|
script.addEventListener("load", () => {
|
|
resolve();
|
|
});
|
|
script.addEventListener("error", (error) => {
|
|
console.error(error);
|
|
reject(`Could not load script at url ${url}.`);
|
|
});
|
|
});
|
|
}
|
|
|
|
module.exports = { addCodeBlockToolbar, refreshHighlighting };
|