From da9add4a10e1e26b76ae705ede431ebb2afb0703 Mon Sep 17 00:00:00 2001 From: Julia Casamitjana <62883011+JuliaCasamitjana@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:52:01 +0200 Subject: [PATCH] Fix ToastUI editor bug preventing manual codeblock insertion Manually inserting a codeblock adding three backticks and hitting enter is not functioning in the ToastUI editor due to an existing bug in the library. This commit implements a workaround to address the issue. --- app/assets/javascripts/markdown_editor.js | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/app/assets/javascripts/markdown_editor.js b/app/assets/javascripts/markdown_editor.js index d8950ae6..4911f602 100644 --- a/app/assets/javascripts/markdown_editor.js +++ b/app/assets/javascripts/markdown_editor.js @@ -6,6 +6,39 @@ * */ +const BACKTICK = 192; +// These counters can be global. Scoping them to each editor becomes redundant +// since they are reset to this state when switching editors. +// Read more: https://github.com/openHPI/codeocean/pull/2242#discussion_r1576617432 +let backtickPressedCount = 0; +let justInsertedCodeBlock = false; + +const deleteSelection = (editor, count) => { + // The backtick is a so-called dead key, which is waiting for further input to be combined with. + // For example a backtick and the letter a are combined to à. + // When we remove a selection ending with a backtick, we want to clear the keyboard buffer, too. + // This ensures that typing a regular character a after this operation is not combined into à, but just inserted as a. + // This solution is taken from https://stackoverflow.com/a/72634132. + editor.blur(); + setTimeout(() => editor.focus()); + // Get current position + const selectionRange = editor.getSelection(); + // Replace the previous `count` characters with an empty string. + // We use a replace function (rather than delete) to avoid issues with line breaks in ToastUi. + // Otherwise, a line break following the cursor position might still be displayed normally, + // but could be removed erroneously from the internal editor state. + // If this happens, code blocks ending with \n``` are not recognized correctly. + editor.replaceSelection( + "", + [selectionRange[0][0], selectionRange[0][1] - count], + [selectionRange[1][0], selectionRange[1][1]] + ); +}; +const resetCount = (withBlock = false) => { + backtickPressedCount = 0; + justInsertedCodeBlock = withBlock; +}; + const initializeMarkdownEditors = () => { const editors = document.querySelectorAll( '[data-behavior="markdown-editor-widget"]' @@ -42,6 +75,39 @@ const initializeMarkdownEditors = () => { const content = toastEditor.getMarkdown(); formInput.value = content; }, + // Fix ToastUI editor bug preventing manual codeblock insertion: + // Manually inserting a codeblock adding three backticks and hitting enter + // is not functioning in the ToastUI editor due to an existing bug in the library. + // Therefore, this `keyup` handler implements a workaround to address the issue. + keyup: (_, event) => { + // Although the use of keyCode seems to be deprecated, the suggested alternatives (key or code) + // work inconsistently across browsers. Using keyCode works flawless for now. + // Read more: https://github.com/openHPI/codeocean/pull/2242#discussion_r1576675620 + if (event.keyCode === BACKTICK) { + backtickPressedCount++; + if (backtickPressedCount === 2) { + // Remove the last two backticks and insert a code block + // The order of operations is important here: Inserting the code block first and then removing + // some backticks won't work, since this would infer with the internal ToastUi editor state. + // With the current solution, we don't mingle with the code block inserted by ToastUi at all. + deleteSelection(toastEditor, 2); + toastEditor.exec("codeBlock"); + resetCount(true); + } + } else if (backtickPressedCount === 1 && justInsertedCodeBlock) { + // We want to improve the usage of our code block fix with the following mechanism. + // Usually, three backticks are required to start a code block. + // However, with our workaround only two backticks are required. + // Out of habit, however, users might still enter three backticks at once, + // not noticing that the code block was already inserted after the second one. + // Thus, we remove one additional backtick entered after starting a code block through our fix. + deleteSelection(toastEditor, 1); + resetCount(); + } else { + // If any other key is pressed, reset the count + resetCount(); + } + }, }, });