mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Show highlighted text in the left pane
This commit is contained in:
		| @@ -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 HltWidget 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"; | ||||
| @@ -181,6 +182,7 @@ export default class DesktopLayout { | ||||
|                     ) | ||||
|                     .child(new RightPaneContainer() | ||||
|                         .child(new TocWidget()) | ||||
|                         .child(new HltWidget()) | ||||
|                         .child(...this.customWidgets.get('right-pane')) | ||||
|                     ) | ||||
|                 ) | ||||
|   | ||||
							
								
								
									
										267
									
								
								src/public/app/widgets/highlighted_text.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								src/public/app/widgets/highlighted_text.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,267 @@ | ||||
| /** | ||||
|  * 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 = `<div class="hlt-widget"> | ||||
|     <style> | ||||
|         .hlt-widget { | ||||
|             padding: 10px; | ||||
|             contain: none;  | ||||
|             overflow: auto; | ||||
|             position: relative; | ||||
|         } | ||||
|          | ||||
|         .hlt > ol { | ||||
|             padding-left: 20px; | ||||
|         } | ||||
|          | ||||
|         .hlt li { | ||||
|             cursor: pointer; | ||||
|             margin-bottom: 3px; | ||||
|             text-align: justify; | ||||
|             text-justify: distribute; | ||||
|         } | ||||
|          | ||||
|         .hlt li:hover { | ||||
|             font-weight: bold; | ||||
|         } | ||||
|          | ||||
|         .close-hlt { | ||||
|             position: absolute; | ||||
|             top: 2px; | ||||
|             right: 2px; | ||||
|         } | ||||
|     </style> | ||||
|  | ||||
|     <span class="hlt"></span> | ||||
| </div>`; | ||||
|  | ||||
| export default class HltWidget 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.hltTemporarilyHidden | ||||
|             && this.noteContext.viewScope.viewMode === 'default'; | ||||
|     } | ||||
|  | ||||
|     async doRenderBody() { | ||||
|         this.$body.empty().append($(TPL)); | ||||
|         this.$hlt = this.$body.find('.hlt'); | ||||
|         this.$body.find('.hlt-widget').append(this.closeHltButton.render()); | ||||
|     } | ||||
|  | ||||
|     async refreshWithNote(note) { | ||||
|         const hltLabel = note.getLabel('hlt'); | ||||
|  | ||||
|         if (hltLabel?.value === 'hide') { | ||||
|             this.toggleInt(false); | ||||
|             this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let $hlt = "", hltColors = [], hltBgColors = []; | ||||
|  | ||||
|         let optionsHltColors = JSON.parse(options.get('highlightedTextColors')); | ||||
|         let optionsHltBgColors = JSON.parse(options.get('highlightedTextBgColors')); | ||||
|         // Check for type text unconditionally in case alwaysShowWidget is set | ||||
|         if (this.note.type === 'text') { | ||||
|             const { content } = await note.getNoteComplement(); | ||||
|             //hltColors/hltBgColors are the colors/background-color that appear in notes and in options  | ||||
|             ({ $hlt, hltColors, hltBgColors } = await this.getHlt(content, optionsHltColors, optionsHltBgColors)); | ||||
|         } | ||||
|         this.$hlt.html($hlt); | ||||
|         this.toggleInt( | ||||
|             ["", "show"].includes(hltLabel?.value) | ||||
|             || hltColors!=""  | ||||
|             || hltBgColors!="" | ||||
|         ); | ||||
|  | ||||
|         this.triggerCommand("reEvaluateRightPaneVisibility"); | ||||
|     } | ||||
|     //Converts color values in RGB, RGBA, or HSL format to hexadecimal format, removing transparency | ||||
|     colorToHex(color) { | ||||
|         function rgbToHex(rgb) { | ||||
|             // Converts color values in RGB or RGBA format to hexadecimal format | ||||
|             var rgba = rgb.match(/\d+/g); | ||||
|             var r = parseInt(rgba[0]); | ||||
|             var g = parseInt(rgba[1]); | ||||
|             var b = parseInt(rgba[2]); | ||||
|             var hex = "#"; | ||||
|             hex += (r < 16 ? "0" : "") + r.toString(16); | ||||
|             hex += (g < 16 ? "0" : "") + g.toString(16); | ||||
|             hex += (b < 16 ? "0" : "") + b.toString(16); | ||||
|             return hex; | ||||
|         } | ||||
|  | ||||
|         function hslToHex(hsl) { | ||||
|             // Convert color values in HSL format to RGB format and then to hexadecimal format | ||||
|             var hslValues = hsl.match(/\d+(\.\d+)?/g); | ||||
|             var h = parseFloat(hslValues[0]) / 360; | ||||
|             var s = parseFloat(hslValues[1]) / 100; | ||||
|             var l = parseFloat(hslValues[2]) / 100; | ||||
|             var r, g, b; | ||||
|  | ||||
|             if (s === 0) { | ||||
|                 r = g = b = l; // achromatic | ||||
|             } else { | ||||
|                 function hueToRgb(p, q, t) { | ||||
|                     if (t < 0) t += 1; | ||||
|                     if (t > 1) t -= 1; | ||||
|                     if (t < 1 / 6) return p + (q - p) * 6 * t; | ||||
|                     if (t < 1 / 2) return q; | ||||
|                     if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; | ||||
|                     return p; | ||||
|                 } | ||||
|  | ||||
|                 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||||
|                 var p = 2 * l - q; | ||||
|                 r = hueToRgb(p, q, h + 1 / 3); | ||||
|                 g = hueToRgb(p, q, h); | ||||
|                 b = hueToRgb(p, q, h - 1 / 3); | ||||
|             } | ||||
|  | ||||
|             var hex = "#"; | ||||
|             hex += (Math.round(r * 255) < 16 ? "0" : "") + Math.round(r * 255).toString(16); | ||||
|             hex += (Math.round(g * 255) < 16 ? "0" : "") + Math.round(g * 255).toString(16); | ||||
|             hex += (Math.round(b * 255) < 16 ? "0" : "") + Math.round(b * 255).toString(16); | ||||
|             return hex; | ||||
|         } | ||||
|         if (color.indexOf("rgb") !== -1) { | ||||
|             return rgbToHex(color); | ||||
|         } else if (color.indexOf("hsl") !== -1) { | ||||
|             return hslToHex(color); | ||||
|         } else { | ||||
|             return ""; | ||||
|         } | ||||
|     } | ||||
|     // Determine whether the highlighted color is in the options, avoid errors caused by errors in color conversion,  | ||||
|     // and the error of each value is acceptable within 2 | ||||
|     hexIsInOptionHexs(targetColor, optionColors){ | ||||
|         for (let i = 0; i < optionColors.length; i++) { | ||||
|             if (Math.abs(parseInt(optionColors[i].slice(1, 3), 16) - parseInt(targetColor.slice(1, 3), 16)) > 2) { continue; } | ||||
|             if (Math.abs(parseInt(optionColors[i].slice(3, 5), 16) - parseInt(targetColor.slice(3, 5), 16)) > 2) { continue; } | ||||
|             if (Math.abs(parseInt(optionColors[i].slice(5, 7), 16) - parseInt(targetColor.slice(5, 7), 16)) > 2) { continue; } | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|     /** | ||||
|      * Builds a jquery table of helight text.       | ||||
|      */ | ||||
|     getHlt(html, optionsHltColors, optionsHltBgColors) { | ||||
|         const hltBCs = $(html).find(`span[style*="background-color"],span[style*="color"]`) | ||||
|         const $hlt = $("<ol>"); | ||||
|         let hltColors = []; | ||||
|         let hltBgColors = []; | ||||
|         for (let hltIndex = 0; hltIndex<hltBCs.length; hltIndex++){ | ||||
|             const hltText = $(hltBCs[hltIndex]).clone(); | ||||
|             const color = $(hltBCs[hltIndex]).css("color"); | ||||
|             const bgColor =$(hltBCs[hltIndex]).css("background-color"); | ||||
|             let liDisplay = false; | ||||
|             var $li = $('<li>'); | ||||
|              | ||||
|             if (color != "") { | ||||
|                 var hexColor = this.colorToHex(color); | ||||
|                 if (this.hexIsInOptionHexs(hexColor,optionsHltColors)) {                    | ||||
|                     $li.html(hltText) | ||||
|                     hltColors.push(hexColor); | ||||
|                     liDisplay=true; | ||||
|                 } | ||||
|             } | ||||
|             if (bgColor != "") { | ||||
|                 var hexBgColor = this.colorToHex(bgColor); | ||||
|                 if (this.hexIsInOptionHexs(hexBgColor,optionsHltBgColors)) { | ||||
|                     //When you need to add a background color, in order to make the display more comfortable, change the background color to transparent | ||||
|                     $li.html(hltText.css("background-color", hexBgColor+"80")) | ||||
|                     hltBgColors.push(hexBgColor); | ||||
|                     liDisplay=true; | ||||
|                 }               | ||||
|             } | ||||
|             if(!liDisplay){ | ||||
|                 $li.css("display","none"); | ||||
|             } | ||||
|             //The font color and background color may be nested or adjacent to each other. At this time, connect the front and back li to avoid interruption | ||||
|             if(hltIndex!=0 && hltBCs[hltIndex-1].nextSibling ===hltBCs[hltIndex] && $hlt.children().last().css("display")!="none"){ | ||||
|                 $hlt.children().last().append($li.html()); | ||||
|             }else{ | ||||
|                 $li.on("click", () => this.jumpToHlt(hltIndex)); | ||||
|                 $hlt.append($li); | ||||
|             } | ||||
|              | ||||
|         }; | ||||
|         return { | ||||
|             $hlt, | ||||
|             hltColors, | ||||
|             hltBgColors | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     async jumpToHlt(hltIndex) { | ||||
|         const isReadOnly = await this.noteContext.isReadOnly(); | ||||
|         if (isReadOnly) { | ||||
|             const $container = await this.noteContext.getContentElement(); | ||||
|             const hltElement = $container.find(`span[style*="background-color"],span[style*="color"]`)[hltIndex]; | ||||
|  | ||||
|             if (hltElement != null) { | ||||
|                 hltElement.scrollIntoView({ behavior: "smooth", block: "center" }); | ||||
|             } | ||||
|         } else { | ||||
|             const textEditor = await this.noteContext.getTextEditor(); | ||||
|             $(textEditor.editing.view.domRoots.values().next().value).find(`span[style*="background-color"],span[style*="color"]`)[hltIndex].scrollIntoView({ | ||||
|                 behavior: "smooth", block: "center" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async closeHltCommand() { | ||||
|         this.noteContext.viewScope.hltTemporarilyHidden = true; | ||||
|         await this.refresh(); | ||||
|         this.triggerCommand('reEvaluateRightPaneVisibility'); | ||||
|     } | ||||
|  | ||||
|     async entitiesReloadedEvent({ loadResults }) { | ||||
|         if (loadResults.isNoteContentReloaded(this.noteId)) { | ||||
|             await this.refresh(); | ||||
|         } else if (loadResults.getAttributes().find(attr => attr.type === 'label' | ||||
|             && (attr.name.toLowerCase().includes('readonly') || attr.name === 'hlt') | ||||
|             && attributeService.isAffecting(attr, this.note))) { | ||||
|             await this.refresh(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class CloseHltButton extends OnClickButtonWidget { | ||||
|     constructor() { | ||||
|         super(); | ||||
|  | ||||
|         this.icon("bx-x") | ||||
|             .title("Close HLT") | ||||
|             .titlePlacement("bottom") | ||||
|             .onClick((widget, e) => { | ||||
|                 e.stopPropagation(); | ||||
|  | ||||
|                 widget.triggerCommand("closeHlt"); | ||||
|             }) | ||||
|             .class("icon-action close-hlt"); | ||||
|     } | ||||
| } | ||||
| @@ -7,6 +7,7 @@ import MaxContentWidthOptions from "./options/appearance/max_content_width.js"; | ||||
| import KeyboardShortcutsOptions from "./options/shortcuts.js"; | ||||
| import HeadingStyleOptions from "./options/text_notes/heading_style.js"; | ||||
| import TableOfContentsOptions from "./options/text_notes/table_of_contents.js"; | ||||
| import HighlightedTextOptions from "./options/text_notes/highlighted_text.js"; | ||||
| import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js"; | ||||
| import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js"; | ||||
| import WrapLinesOptions from "./options/code_notes/wrap_lines.js"; | ||||
| @@ -61,6 +62,7 @@ const CONTENT_WIDGETS = { | ||||
|     _optionsTextNotes: [ | ||||
|         HeadingStyleOptions, | ||||
|         TableOfContentsOptions, | ||||
|         HighlightedTextOptions, | ||||
|         TextAutoReadOnlySizeOptions | ||||
|     ], | ||||
|     _optionsCodeNotes: [ | ||||
|   | ||||
| @@ -0,0 +1,90 @@ | ||||
| import OptionsWidget from "../options_widget.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <div class="options-section"> | ||||
|     <style> | ||||
|         .hlt-checkbox-label { | ||||
|             display: inline-block; | ||||
|             min-width: 8em; | ||||
|         } | ||||
|         .options-section{ | ||||
|             max-width: 46em; | ||||
|         } | ||||
|     </style> | ||||
|     <h4>Highlighted Text</h4> | ||||
|      | ||||
|     Displays highlighted text in the left pane. You can customize the highlighted text displayed in the left pane: | ||||
|     <br><strong>Text color:</strong><br> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#000000"> Dark  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4d4d4d"> Dim grey  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#999999"> Grey  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#e6e6e6"> Light grey  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#ffffff"> White  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#e64c4c"> Red  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#e6994c"> Orange  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#e6e64c"> Yellow  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#99e64c"> Light green  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4ce64c"> Green  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4ce699"> Aquamarine  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4ce6e6"> Turquoise  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4c99e6"> Light blue  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#4c4ce6"> Blue  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-color" value="#994ce6"> Purple  </label> | ||||
| <br><strong>Background color:</strong><br> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#000000"> Dark </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4d4d4d"> Dim grey </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#999999"> Grey </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#e6e6e6"> Light grey </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#ffffff"> White </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#e64c4c"> Red  </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#e6994c"> Orange </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#e6e64c"> Yellow </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#99e64c"> Light green </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4ce64c"> Green </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4ce699"> Aquamarine </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4ce6e6"> Turquoise </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4c99e6"> Light blue </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#4c4ce6"> Blue </label> | ||||
| <label class='hlt-checkbox-label'><input type="checkbox" class="hlt-background-color" value="#994ce6"> Purple </label>   | ||||
| </div>`; | ||||
|  | ||||
| export default class HighlightedTextOptions extends OptionsWidget { | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
|         this.$hltColors = this.$widget.find(".hlt-color"); | ||||
|         this.$hltColors.on('change', () => { | ||||
|             const hltColorVals=this.$widget.find('input.hlt-color[type="checkbox"]:checked').map(function() { | ||||
|                 return this.value; | ||||
|               }).get(); | ||||
|             this.updateOption('highlightedTextColors', JSON.stringify(hltColorVals)); | ||||
|  | ||||
|             }); | ||||
|         this.$hltBgColors = this.$widget.find(".hlt-background-color"); | ||||
|         this.$hltBgColors.on('change', () =>{ | ||||
|             const hltBgColorVals=this.$widget.find('input.hlt-background-color[type="checkbox"]:checked').map(function() { | ||||
|                 return this.value; | ||||
|               }).get(); | ||||
|             this.updateOption('highlightedTextBgColors', JSON.stringify(hltBgColorVals)); | ||||
|         }); | ||||
|          | ||||
|     } | ||||
|  | ||||
|     async optionsLoaded(options) { | ||||
|         const hltColorVals=JSON.parse(options.highlightedTextColors); | ||||
|         const hltBgColorVals=JSON.parse(options.highlightedTextBgColors); | ||||
|         this.$widget.find('input.hlt-color[type="checkbox"]').each(function () { | ||||
|             if ($.inArray($(this).val(), hltColorVals) !== -1) { | ||||
|                     $(this).prop("checked", true); | ||||
|             } else { | ||||
|                     $(this).prop("checked", false); | ||||
|             } | ||||
|         }); | ||||
|         this.$widget.find('input.hlt-background-color[type="checkbox"]').each(function () { | ||||
|             if ($.inArray($(this).val(), hltBgColorVals) !== -1) { | ||||
|                     $(this).prop("checked", true); | ||||
|             } else { | ||||
|                     $(this).prop("checked", false); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -60,6 +60,8 @@ const ALLOWED_OPTIONS = new Set([ | ||||
|     'compressImages', | ||||
|     'downloadImagesAutomatically', | ||||
|     'minTocHeadings', | ||||
|     'highlightedTextColors', | ||||
|     'highlightedTextBgColors', | ||||
|     'checkForUpdates', | ||||
|     'disableTray', | ||||
|     'customSearchEngineName', | ||||
|   | ||||
| @@ -87,6 +87,8 @@ const defaultOptions = [ | ||||
|     { name: 'compressImages', value: 'true', isSynced: true }, | ||||
|     { name: 'downloadImagesAutomatically', value: 'true', isSynced: true }, | ||||
|     { name: 'minTocHeadings', value: '5', isSynced: true }, | ||||
|     { name: 'highlightedTextColors', value: '["#e64c4c","#e6994c","#e6e64c","#99e64c","#4ce64c","#4ce699","#4ce6e6","#4c99e6","#4c4ce6","#994ce6"]', isSynced: true }, | ||||
|     { name: 'highlightedTextBgColors', value: '["#e64c4c","#e6994c","#e6e64c","#99e64c","#4ce64c","#4ce699","#4ce6e6","#4c99e6","#4c4ce6","#994ce6"]', isSynced: true }, | ||||
|     { name: 'checkForUpdates', value: 'true', isSynced: true }, | ||||
|     { name: 'disableTray', value: 'false', isSynced: false }, | ||||
|     { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user