From cb1ccc19e58c56ba4cc7ec37830d9b977127edbc Mon Sep 17 00:00:00 2001 From: thecodrr Date: Sun, 17 Oct 2021 13:04:27 +0500 Subject: [PATCH] feat: improve code-block ux --- packages/tinymce-plugins/codeblock/index.js | 40 ++++++++++++++++--- packages/tinymce-plugins/codeblock/toolbar.js | 31 ++++++++------ packages/tinymce-plugins/utils.js | 18 +++++++++ 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/packages/tinymce-plugins/codeblock/index.js b/packages/tinymce-plugins/codeblock/index.js index c3c1ff613..cf2636e10 100644 --- a/packages/tinymce-plugins/codeblock/index.js +++ b/packages/tinymce-plugins/codeblock/index.js @@ -2,11 +2,13 @@ const { addPluginToPluginManager, getCharacterRange, moveCaretTo, + getCurrentLine, } = require("../utils"); const { createCodeBlock, isCodeBlock, TAGNAME, state } = require("./utils"); const { addCodeBlockToolbar, refreshHighlighting } = require("./toolbar"); -const TAB = " "; +const TAB = `  `; +const TAB_LENGTH = 2; const EMPTY_LINE = "


"; /** @@ -44,6 +46,7 @@ var toggleCodeBlock = function (editor, api, type) { } else { var content = editor.selection.getContent({ format: "text" }); //.replace(/^\n/gm, ""); if (type === "shortcut") content = "
"; + if (!content) content = "
"; insertCodeBlock(editor, content); } }; @@ -81,6 +84,12 @@ function blurCodeBlock(editor, block) { } var isCreatingCodeBlock = false; +/** + * + * @param {*} api + * @param {import("tinymce").Editor} editor + * @returns + */ var registerHandlers = function (api, editor) { function onNodeChanged(event) { api.setActive(event.element.tagName === TAGNAME); @@ -128,13 +137,34 @@ var registerHandlers = function (api, editor) { // we have to handle all the logic manually. function onKeyDown(e) { const node = state.activeBlock; - if (!node || e.code !== "Tab") return; + if (!node) return; + if (e.code === "Tab") { + e.preventDefault(); + handleTab(e, node); + } else if (e.ctrlKey && e.code === "KeyA") { + e.preventDefault(); + handleCtrlA(node); + } else if (e.code === "Enter") { + handleEnter(node); + } + } + + function handleEnter(node) { + const currentLine = getCurrentLine(node); + const indent = + (currentLine.length - currentLine.trimStart().length) / TAB_LENGTH; + editor.insertContent(TAB.repeat(indent)); + } + + function handleCtrlA(node) { + editor.selection.select(node, true); + } + + function handleTab(e, node) { const characterRange = getCharacterRange(node); if (!characterRange) return; - e.preventDefault(); - // Shift + Tab = Deindent on all major platforms const isDeindent = e.shiftKey; const text = node.textContent; @@ -171,7 +201,7 @@ var registerHandlers = function (api, editor) { moveCaretTo(node, characterRange.start, endIndex); } else { // TODO: handle line deindent - editor.insertContent(TAB); + editor.selection.setContent(TAB); } } else { editor.insertContent(TAB); diff --git a/packages/tinymce-plugins/codeblock/toolbar.js b/packages/tinymce-plugins/codeblock/toolbar.js index 0dcbd6923..fcdbfeb56 100644 --- a/packages/tinymce-plugins/codeblock/toolbar.js +++ b/packages/tinymce-plugins/codeblock/toolbar.js @@ -72,7 +72,7 @@ function changeLanguageSelectLabel(text) { function parseCodeblockLanguage(node) { if (!node || node.tagName !== TAGNAME) return; - const languageAliases = getLanguageFromClassName(node.className).split("-"); + const languageAliases = getLanguageFromClassList(node).split("-"); if (languageAliases.length <= 1) return; return hljs.getLanguage(languageAliases[1]); } @@ -93,26 +93,33 @@ function applyHighlighting(editor, language) { const alias = language.aliases[0]; persistSelection(node, () => { - node.innerHTML = hljs.highlight(node.innerText, { + const code = hljs.highlight(node.innerText, { language: alias, - }).value; + }); + node.innerHTML = code.value.replace(/\n/gm, "
"); editor.save(); }); - changeCodeblockClassName(node, ` language-${alias} `); + changeCodeblockClassName(node, `language-${alias}`); } function changeCodeblockClassName(node, className) { - node.className = node.className.replace( - getLanguageFromClassName(node.className), - className - ); + const language = getLanguageFromClassList(node); + if (!!language) + node.classList.replace(getLanguageFromClassList(node), className); + else node.classList.add(className); } -function getLanguageFromClassName(className) { - const classes = className.split(" "); - const languageKey = classes.find((c) => c.startsWith("lang")); - return languageKey || ""; +/** + * + * @param {Element} node + */ +function getLanguageFromClassList(node) { + for (let className of node.classList.values()) { + if (className.startsWith("language") || className.startsWith("lang")) + return className; + } + return ""; } function refreshHighlighting(editor) { diff --git a/packages/tinymce-plugins/utils.js b/packages/tinymce-plugins/utils.js index e3bb27ff7..d7746cca4 100644 --- a/packages/tinymce-plugins/utils.js +++ b/packages/tinymce-plugins/utils.js @@ -35,6 +35,23 @@ function moveCaretTo(node, index, endIndex) { rangy.getSelection().restoreCharacterRanges(node, [newCharacterRange]); } +function getCurrentLine(node) { + const characterRange = getCharacterRange(node); + const lines = node.innerText.split("\n"); + + let currentLine = ""; + let prevLength = 0; + for (let line of lines) { + let length = prevLength + line.length + 1; + if (characterRange.start === length) { + currentLine = line; + break; + } + prevLength += line.length + 1; + } + return currentLine; +} + function persistSelection(node, action) { let saved = rangy.getSelection().saveCharacterRanges(node); action(); @@ -52,6 +69,7 @@ function addPluginToPluginManager(name, register) { } module.exports = { + getCurrentLine, getCharacterRange, moveCaretTo, persistSelection,