diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 62e6b3ed1..e5c440cdc 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -44,6 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import SharedInfoWidget from "../widgets/shared_info.js"; import FindWidget from "../widgets/find.js"; import TocWidget from "../widgets/toc.js"; +import HighlightedTextWidget from "../widgets/highlighted_text.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import AboutDialog from "../widgets/dialogs/about.js"; import HelpDialog from "../widgets/dialogs/help.js"; @@ -184,6 +185,7 @@ export default class DesktopLayout { ) .child(new RightPaneContainer() .child(new TocWidget()) + .child(new HighlightedTextWidget()) .child(...this.customWidgets.get('right-pane')) ) ) diff --git a/src/public/app/widgets/highlighted_text.js b/src/public/app/widgets/highlighted_text.js new file mode 100644 index 000000000..daced39d4 --- /dev/null +++ b/src/public/app/widgets/highlighted_text.js @@ -0,0 +1,255 @@ +/** + * 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"; + +const TPL = `
`; + +export default class HighlightedTextWidget extends RightPanelWidget { + constructor() { + super(); + + this.closeHltButton = new CloseHltButton(); + this.child(this.closeHltButton); + } + + get widgetTitle() { + return "Highlighted Text"; + } + + isEnabled() { + return super.isEnabled() + && this.note.type === 'text' + && !this.noteContext.viewScope.highlightedTextTemporarilyHidden + && this.noteContext.viewScope.viewMode === 'default'; + } + + async doRenderBody() { + this.$body.empty().append($(TPL)); + this.$hlt = this.$body.find('.highlighted-text'); + this.$body.find('.highlighted-text-widget').append(this.closeHltButton.render()); + } + + async refreshWithNote(note) { + /*The reason for adding highlightedTextPreviousVisible is to record whether the previous state of the highlightedText 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.highlightedTextPreviousVisible == true) { + this.toggleInt(true); + } else { + this.toggleInt(false); + } + const hltLabel = note.getLabel('hideHighlightWidget'); + + const optionsHlt = JSON.parse(options.get('highlightedText')); + + if (hltLabel?.value == "" || hltLabel?.value === "true" || optionsHlt == "") { + this.toggleInt(false); + this.triggerCommand("reEvaluateRightPaneVisibility"); + return; + } + + let $hlt = "", hltLiCount = -1; + // Check for type text unconditionally in case alwaysShowWidget is set + if (this.note.type === 'text') { + const { content } = await note.getNoteComplement(); + ({ $hlt, hltLiCount } = await this.getHlt(content, optionsHlt)); + } + this.$hlt.html($hlt); + if ([undefined, "false"].includes(hltLabel?.value) && hltLiCount > 0) { + this.toggleInt(true); + this.noteContext.viewScope.highlightedTextPreviousVisible = true; + } else { + this.toggleInt(false); + this.noteContext.viewScope.highlightedTextPreviousVisible = false; + } + + this.triggerCommand("reEvaluateRightPaneVisibility"); + } + + /** + * Builds a table of helight text. + */ + getHlt(html, optionsHlt) { + // 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 optionsHlt: '["bold","italic","underline","color","bgColor"]' + // element priority: span>i>strong>u + let findSubStr="", combinedRegexStr = ""; + if (optionsHlt.indexOf("bgColor") >= 0){ + findSubStr+=`,span[style*="background-color"]`; + combinedRegexStr+=`|${regex1.source}`; + } + if (optionsHlt.indexOf("color") >= 0){ + findSubStr+=`,span[style*="color"]`; + combinedRegexStr+=`|${regex2.source}`; + } + if (optionsHlt.indexOf("italic") >= 0){ + findSubStr+=`,i`; + combinedRegexStr+=`|${regex3.source}`; + } + if (optionsHlt.indexOf("bold") >= 0){ + findSubStr+=`,strong`; + combinedRegexStr+=`|${regex4.source}`; + } + if (optionsHlt.indexOf("underline") >= 0){ + findSubStr+=`,u`; + combinedRegexStr+=`|${regex5.source}`; + } + + findSubStr = findSubStr.substring(1) + combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; + const combinedRegex = new RegExp(combinedRegexStr, 'gi'); + let $hlt = $("