/** * Widget: Show highlighted text in the right pane * * By design, there's no support for nonsensical or malformed constructs: * - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries */ import attributeService from "../services/attributes.js"; import RightPanelWidget from "./right_panel_widget.js"; import options from "../services/options.js"; import OnClickButtonWidget from "./buttons/onclick_button.js"; import appContext from "../components/app_context.js"; import libraryLoader from "../services/library_loader.js"; const TPL = `
`; export default class HighlightsListWidget extends RightPanelWidget { get widgetTitle() { return "Highlights List"; } get widgetButtons() { return [ new OnClickButtonWidget() .icon("bx-cog") .title("Options") .titlePlacement("left") .onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true })) .class("icon-action"), new OnClickButtonWidget() .icon("bx-x") .titlePlacement("left") .onClick(widget => widget.triggerCommand("closeHlt")) .class("icon-action") ]; } isEnabled() { return super.isEnabled() && this.note.type === 'text' && !this.noteContext.viewScope.highlightsListTemporarilyHidden && this.noteContext.viewScope.viewMode === 'default'; } async doRenderBody() { this.$body.empty().append($(TPL)); this.$highlightsList = this.$body.find('.highlights-list'); } async refreshWithNote(note) { /* The reason for adding highlightsListPreviousVisible is to record whether the previous state of the highlightsList is hidden or displayed, and then let it be displayed/hidden at the initial time. If there is no such value, when the right panel needs to display toc but not highlighttext, every time the note content is changed, highlighttext Widget will appear and then close immediately, because getHlt function will consume time */ if (this.noteContext.viewScope.highlightsListPreviousVisible) { this.toggleInt(true); } else { this.toggleInt(false); } const optionsHighlightsList = JSON.parse(options.get('highlightsList')); if (note.isLabelTruthy('hideHighlightWidget') || !optionsHighlightsList.length) { this.toggleInt(false); this.triggerCommand("reEvaluateRightPaneVisibility"); return; } let $highlightsList = "", hlLiCount = -1; // Check for type text unconditionally in case alwaysShowWidget is set if (this.note.type === 'text') { const { content } = await note.getNoteComplement(); ({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList)); } this.$highlightsList.empty().append($highlightsList); if (hlLiCount > 0) { this.toggleInt(true); this.noteContext.viewScope.highlightsListPreviousVisible = true; } else { this.toggleInt(false); this.noteContext.viewScope.highlightsListPreviousVisible = false; } this.triggerCommand("reEvaluateRightPaneVisibility"); } extractOuterTag(htmlStr) { if (htmlStr === null) { return null } // Regular expressions that match only the outermost tag const regex = /^<([a-zA-Z]+)([^>]*)>/; const match = htmlStr.match(regex); if (match) { const tagName = match[1].toLowerCase(); // Extract tag name const attributes = match[2].trim(); // Extract label attributes return { tagName, attributes }; } return null; } areOuterTagsConsistent(str1, str2) { const tag1 = this.extractOuterTag(str1); const tag2 = this.extractOuterTag(str2); // If one of them has no label, returns false if (!tag1 || !tag2) { return false; } // Compare tag names and attributes to see if they are the same return tag1.tagName === tag2.tagName && tag1.attributes === tag2.attributes; } /** * Rendering formulas in strings using katex * * @param {string} html Note's html content * @returns {string} The HTML content with mathematical formulas rendered by KaTeX. */ async replaceMathTextWithKatax(html) { const mathTextRegex = /\\\(([\s\S]*?)\\\)<\/span>/g; var matches = [...html.matchAll(mathTextRegex)]; let modifiedText = html; if (matches.length > 0) { // Process all matches asynchronously for (const match of matches) { let latexCode = match[1]; let rendered; try { rendered = katex.renderToString(latexCode, { throwOnError: false }); } catch (e) { if (e instanceof ReferenceError && e.message.includes('katex is not defined')) { // Load KaTeX if it is not already loaded await libraryLoader.requireLibrary(libraryLoader.KATEX); try { rendered = katex.renderToString(latexCode, { throwOnError: false }); } catch (renderError) { console.error("KaTeX rendering error after loading library:", renderError); rendered = match[0]; // Fall back to original if error persists } } else { console.error("KaTeX rendering error:", e); rendered = match[0]; // Fall back to original on error } } // Replace the matched formula in the modified text modifiedText = modifiedText.replace(match[0], rendered); } } return modifiedText; } async getHighlightList(content, optionsHighlightsList) { // matches a span containing background-color const regex1 = /]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; // matches a span containing color const regex2 = /]*style\s*=\s*[^>]*[^-]color:[^>]*?>[\s\S]*?<\/span>/gi; // match italics const regex3 = /[\s\S]*?<\/i>/gi; // match bold const regex4 = /[\s\S]*?<\/strong>/gi; // match underline const regex5 = /[\s\S]*?<\/u>/g; // Possible values in optionsHighlightsList: '["bold","italic","underline","color","bgColor"]' // element priority: span>i>strong>u let findSubStr = "", combinedRegexStr = ""; if (optionsHighlightsList.includes("bgColor")) { findSubStr += `,span[style*="background-color"]:not(section.include-note span[style*="background-color"])`; combinedRegexStr += `|${regex1.source}`; } if (optionsHighlightsList.includes("color")) { findSubStr += `,span[style*="color"]:not(section.include-note span[style*="color"])`; combinedRegexStr += `|${regex2.source}`; } if (optionsHighlightsList.includes("italic")) { findSubStr += `,i:not(section.include-note i)`; combinedRegexStr += `|${regex3.source}`; } if (optionsHighlightsList.includes("bold")) { findSubStr += `,strong:not(section.include-note strong)`; combinedRegexStr += `|${regex4.source}`; } if (optionsHighlightsList.includes("underline")) { findSubStr += `,u:not(section.include-note u)`; combinedRegexStr += `|${regex5.source}`; } findSubStr = findSubStr.substring(1) combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; const combinedRegex = new RegExp(combinedRegexStr, 'gi'); const $highlightsList = $("