Notes/src/public/app/widgets/highlighted_text.js

268 lines
9.7 KiB
JavaScript
Raw Normal View History

2023-05-31 18:32:33 +08:00
/**
* 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");
}
}