refactoring of highlight list

This commit is contained in:
zadam 2023-06-04 17:46:37 +02:00
parent eff3e1df85
commit c177aaa901
3 changed files with 104 additions and 100 deletions

View File

@ -44,7 +44,7 @@ import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
import SharedInfoWidget from "../widgets/shared_info.js"; import SharedInfoWidget from "../widgets/shared_info.js";
import FindWidget from "../widgets/find.js"; import FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js"; import TocWidget from "../widgets/toc.js";
import HighlightedTextWidget from "../widgets/highlighted_text.js"; import HighlightsListWidget from "../widgets/highlights_list.js";
import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js"; import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js";
import AboutDialog from "../widgets/dialogs/about.js"; import AboutDialog from "../widgets/dialogs/about.js";
import HelpDialog from "../widgets/dialogs/help.js"; import HelpDialog from "../widgets/dialogs/help.js";
@ -185,7 +185,7 @@ export default class DesktopLayout {
) )
.child(new RightPaneContainer() .child(new RightPaneContainer()
.child(new TocWidget()) .child(new TocWidget())
.child(new HighlightedTextWidget()) .child(new HighlightsListWidget())
.child(...this.customWidgets.get('right-pane')) .child(...this.customWidgets.get('right-pane'))
) )
) )

View File

@ -1,7 +1,7 @@
/** /**
* Widget: Show highlighted text in the right pane * Widget: Show highlighted text in the right pane
* *
* By design there's no support for nonsensical or malformed constructs: * 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 * - 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
*/ */
@ -10,20 +10,20 @@ import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js"; import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js"; import OnClickButtonWidget from "./buttons/onclick_button.js";
const TPL = `<div class="highlighted-text-widget"> const TPL = `<div class="highlists-list-widget">
<style> <style>
.highlighted-text-widget { .highlists-list-widget {
padding: 10px; padding: 10px;
contain: none; contain: none;
overflow: auto; overflow: auto;
position: relative; position: relative;
} }
.highlighted-text > ol { .highlists-list > ol {
padding-left: 20px; padding-left: 20px;
} }
.highlighted-text li { .highlists-list li {
cursor: pointer; cursor: pointer;
margin-bottom: 3px; margin-bottom: 3px;
text-align: justify; text-align: justify;
@ -32,21 +32,21 @@ const TPL = `<div class="highlighted-text-widget">
hyphens: auto; hyphens: auto;
} }
.highlighted-text li:hover { .highlists-list li:hover {
font-weight: bold; font-weight: bold;
} }
.close-highlighted-text { .close-highlists-list {
position: absolute; position: absolute;
top: 2px; top: 2px;
right: 2px; right: 2px;
} }
</style> </style>
<span class="highlighted-text"></span> <span class="highlists-list"></span>
</div>`; </div>`;
export default class HighlightedTextWidget extends RightPanelWidget { export default class HighlightsListWidget extends RightPanelWidget {
constructor() { constructor() {
super(); super();
@ -67,38 +67,38 @@ export default class HighlightedTextWidget extends RightPanelWidget {
async doRenderBody() { async doRenderBody() {
this.$body.empty().append($(TPL)); this.$body.empty().append($(TPL));
this.$hlt = this.$body.find('.highlighted-text'); this.$highlightsList = this.$body.find('.highlists-list');
this.$body.find('.highlighted-text-widget').append(this.closeHltButton.render()); this.$body.find('.highlists-list-widget').append(this.closeHltButton.render());
} }
async refreshWithNote(note) { async refreshWithNote(note) {
/*The reason for adding highlightedTextPreviousVisible is to record whether the previous state of the highlightedText is hidden or displayed, /* The reason for adding highlightedTextPreviousVisible is to record whether the previous state
* and then let it be displayed/hidden at the initial time. 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, If there is no such value, when the right panel needs to display toc but not highlighttext,
* highlighttext Widget will appear and then close immediately, because getHlt function will consume time*/ every time the note content is changed, highlighttext Widget will appear and then close immediately,
if (this.noteContext.viewScope.highlightedTextPreviousVisible == true) { because getHlt function will consume time */
if (this.noteContext.viewScope.highlightedTextPreviousVisible) {
this.toggleInt(true); this.toggleInt(true);
} else { } else {
this.toggleInt(false); this.toggleInt(false);
} }
const hltLabel = note.getLabel('hideHighlightWidget');
const optionsHlt = JSON.parse(options.get('highlightedText')); const optionsHlt = JSON.parse(options.get('highlightedText'));
if (hltLabel?.value == "" || hltLabel?.value === "true" || optionsHlt == "") { if (note.isLabelTruthy('hideHighlightWidget') || !optionsHlt) {
this.toggleInt(false); this.toggleInt(false);
this.triggerCommand("reEvaluateRightPaneVisibility"); this.triggerCommand("reEvaluateRightPaneVisibility");
return; return;
} }
let $hlt = "", hltLiCount = -1; let $highlightsList = "", hltLiCount = -1;
// Check for type text unconditionally in case alwaysShowWidget is set // Check for type text unconditionally in case alwaysShowWidget is set
if (this.note.type === 'text') { if (this.note.type === 'text') {
const { content } = await note.getNoteComplement(); const {content} = await note.getNoteComplement();
({ $hlt, hltLiCount } = await this.getHlt(content, optionsHlt)); ({$highlightsList, hltLiCount} = this.getHighlightList(content, optionsHlt));
} }
this.$hlt.html($hlt); this.$highlightsList.empty().append($highlightsList);
if ([undefined, "false"].includes(hltLabel?.value) && hltLiCount > 0) { if (hltLiCount > 0) {
this.toggleInt(true); this.toggleInt(true);
this.noteContext.viewScope.highlightedTextPreviousVisible = true; this.noteContext.viewScope.highlightedTextPreviousVisible = true;
} else { } else {
@ -109,12 +109,9 @@ export default class HighlightedTextWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility"); this.triggerCommand("reEvaluateRightPaneVisibility");
} }
/** getHighlightList(content, optionsHlt) {
* Builds a table of helight text.
*/
getHlt(html, optionsHlt) {
// matches a span containing background-color // matches a span containing background-color
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
// matches a span containing color // matches a span containing color
const regex2 = /<span[^>]*style\s*=\s*[^>]*[^-]color:[^>]*?>[\s\S]*?<\/span>/gi; const regex2 = /<span[^>]*style\s*=\s*[^>]*[^-]color:[^>]*?>[\s\S]*?<\/span>/gi;
// match italics // match italics
@ -125,97 +122,103 @@ export default class HighlightedTextWidget extends RightPanelWidget {
const regex5 = /<u>[\s\S]*?<\/u>/g; const regex5 = /<u>[\s\S]*?<\/u>/g;
// Possible values in optionsHlt '["bold","italic","underline","color","bgColor"]' // Possible values in optionsHlt '["bold","italic","underline","color","bgColor"]'
// element priority span>i>strong>u // element priority span>i>strong>u
let findSubStr="", combinedRegexStr = ""; let findSubStr = "", combinedRegexStr = "";
if (optionsHlt.indexOf("bgColor") >= 0){ if (optionsHlt.includes("bgColor")) {
findSubStr+=`,span[style*="background-color"]`; findSubStr += `,span[style*="background-color"]`;
combinedRegexStr+=`|${regex1.source}`; combinedRegexStr += `|${regex1.source}`;
} }
if (optionsHlt.indexOf("color") >= 0){ if (optionsHlt.includes("color")) {
findSubStr+=`,span[style*="color"]`; findSubStr += `,span[style*="color"]`;
combinedRegexStr+=`|${regex2.source}`; combinedRegexStr += `|${regex2.source}`;
} }
if (optionsHlt.indexOf("italic") >= 0){ if (optionsHlt.includes("italic")) {
findSubStr+=`,i`; findSubStr += `,i`;
combinedRegexStr+=`|${regex3.source}`; combinedRegexStr += `|${regex3.source}`;
} }
if (optionsHlt.indexOf("bold") >= 0){ if (optionsHlt.indexOf("bold")) {
findSubStr+=`,strong`; findSubStr += `,strong`;
combinedRegexStr+=`|${regex4.source}`; combinedRegexStr += `|${regex4.source}`;
} }
if (optionsHlt.indexOf("underline") >= 0){ if (optionsHlt.includes("underline")) {
findSubStr+=`,u`; findSubStr += `,u`;
combinedRegexStr+=`|${regex5.source}`; combinedRegexStr += `|${regex5.source}`;
} }
findSubStr = findSubStr.substring(1) findSubStr = findSubStr.substring(1)
combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`; combinedRegexStr = `(` + combinedRegexStr.substring(1) + `)`;
const combinedRegex = new RegExp(combinedRegexStr, 'gi'); const combinedRegex = new RegExp(combinedRegexStr, 'gi');
let $hlt = $("<ol>"); const $highlightsList = $("<ol>");
let prevEndIndex = -1, hltLiCount = 0; let prevEndIndex = -1, hltLiCount = 0;
for (let match = null, hltIndex=0; ((match = combinedRegex.exec(html)) !== null); hltIndex++) { for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
var subHtml = match[0]; const subHtml = match[0];
const startIndex = match.index; const startIndex = match.index;
const endIndex = combinedRegex.lastIndex; const endIndex = combinedRegex.lastIndex;
if (prevEndIndex != -1 && startIndex === prevEndIndex) { if (prevEndIndex !== -1 && startIndex === prevEndIndex) {
//If the previous element is connected to this element in HTML, then concatenate them into one. // If the previous element is connected to this element in HTML, then concatenate them into one.
$hlt.children().last().append(subHtml); $highlightsList.children().last().append(subHtml);
} else { } else {
//hide li if its text content is empty // TODO: can't be done with $(subHtml).text()?
if ([...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim() != ""){ const hasText = [...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim();
var $li = $('<li>');
$li.html(subHtml); if (hasText) {
$li.on("click", () => this.jumpToHlt(findSubStr,hltIndex)); $highlightsList.append(
$hlt.append($li); $('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightedText(findSubStr, hltIndex))
);
hltLiCount++; hltLiCount++;
}else{ } else {
continue // hide li if its text content is empty
continue;
} }
} }
prevEndIndex = endIndex; prevEndIndex = endIndex;
} }
return { return {
$hlt, $highlightsList,
hltLiCount hltLiCount
}; };
} }
async jumpToHlt(findSubStr,hltIndex) {
async jumpToHighlightedText(findSubStr, itemIndex) {
const isReadOnly = await this.noteContext.isReadOnly(); const isReadOnly = await this.noteContext.isReadOnly();
let targetElement; let targetElement;
if (isReadOnly) { if (isReadOnly) {
const $container = await this.noteContext.getContentElement(); const $container = await this.noteContext.getContentElement();
targetElement=$container.find(findSubStr).filter(function() { targetElement = $container.find(findSubStr).filter(function () {
if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) {
let color = this.style.color; let color = this.style.color;
return $(this).prop('tagName')=="SPAN" && color==""?false:true; return !($(this).prop('tagName') === "SPAN" && color === "");
}else{ } else {
return true; return true;
} }
}).filter(function() { }).filter(function () {
return $(this).parent(findSubStr).length === 0 return $(this).parent(findSubStr).length === 0
&& $(this).parent().parent(findSubStr).length === 0 && $(this).parent().parent(findSubStr).length === 0
&& $(this).parent().parent().parent(findSubStr).length === 0 && $(this).parent().parent().parent(findSubStr).length === 0
&& $(this).parent().parent().parent().parent(findSubStr).length === 0; && $(this).parent().parent().parent().parent(findSubStr).length === 0;
}) })
} else { } else {
const textEditor = await this.noteContext.getTextEditor(); const textEditor = await this.noteContext.getTextEditor();
targetElement=$(textEditor.editing.view.domRoots.values().next().value).find(findSubStr).filter(function() { targetElement = $(textEditor.editing.view.domRoots.values().next().value).find(findSubStr).filter(function () {
// When finding span[style*="color"] but not looking for span[style*="background-color"], // When finding span[style*="color"] but not looking for span[style*="background-color"],
// the background-color error will be regarded as color, so it needs to be filtered // the background-color error will be regarded as color, so it needs to be filtered
if (findSubStr.indexOf("color")>=0 && findSubStr.indexOf("background-color")<0){ if (findSubStr.indexOf("color") >= 0 && findSubStr.indexOf("background-color") < 0) {
let color = this.style.color; let color = this.style.color;
return $(this).prop('tagName')=="SPAN" && color==""?false:true; return !($(this).prop('tagName') === "SPAN" && color === "");
}else{ } else {
return true; return true;
} }
}).filter(function() { }).filter(function () {
//Need to filter out the child elements of the element that has been found // Need to filter out the child elements of the element that has been found
return $(this).parent(findSubStr).length === 0 return $(this).parent(findSubStr).length === 0
&& $(this).parent().parent(findSubStr).length === 0 && $(this).parent().parent(findSubStr).length === 0
&& $(this).parent().parent().parent(findSubStr).length === 0 && $(this).parent().parent().parent(findSubStr).length === 0
&& $(this).parent().parent().parent().parent(findSubStr).length === 0; && $(this).parent().parent().parent().parent(findSubStr).length === 0;
}) })
} }
targetElement[hltIndex].scrollIntoView({ targetElement[itemIndex].scrollIntoView({
behavior: "smooth", block: "center" behavior: "smooth", block: "center"
}); });
} }
@ -226,7 +229,7 @@ export default class HighlightedTextWidget extends RightPanelWidget {
this.triggerCommand('reEvaluateRightPaneVisibility'); this.triggerCommand('reEvaluateRightPaneVisibility');
} }
async entitiesReloadedEvent({ loadResults }) { async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) { if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh(); await this.refresh();
} else if (loadResults.getAttributes().find(attr => attr.type === 'label' } else if (loadResults.getAttributes().find(attr => attr.type === 'label'
@ -237,7 +240,6 @@ export default class HighlightedTextWidget extends RightPanelWidget {
} }
} }
class CloseHltButton extends OnClickButtonWidget { class CloseHltButton extends OnClickButtonWidget {
constructor() { constructor() {
super(); super();
@ -250,6 +252,6 @@ class CloseHltButton extends OnClickButtonWidget {
widget.triggerCommand("closeHlt"); widget.triggerCommand("closeHlt");
}) })
.class("icon-action close-highlighted-text"); .class("icon-action close-highlists-list");
} }
} }

View File

@ -4,13 +4,15 @@ const TPL = `
<div class="options-section"> <div class="options-section">
<h4>Highlighted Text</h4> <h4>Highlighted Text</h4>
You can customize the highlighted text displayed in the right panel:<br> <p>You can customize the highlighted text displayed in the right panel:</p>
<label><input type="checkbox" class="highlighted-text-check" value="bold"> Bold font &nbsp;</label> </div>
<label><input type="checkbox" class="highlighted-text-check" value="italic"> Italic font &nbsp;</label> <label><input type="checkbox" class="highlighted-text-check" value="bold"> Bold font &nbsp;</label>
<label><input type="checkbox" class="highlighted-text-check" value="underline"> Underlined font &nbsp;</label> <label><input type="checkbox" class="highlighted-text-check" value="italic"> Italic font &nbsp;</label>
<label><input type="checkbox" class="highlighted-text-check" value="color"> Font with color &nbsp;</label> <label><input type="checkbox" class="highlighted-text-check" value="underline"> Underlined font &nbsp;</label>
<label><input type="checkbox" class="highlighted-text-check" value="bgColor"> Font with background color &nbsp;</label> <label><input type="checkbox" class="highlighted-text-check" value="color"> Font with color &nbsp;</label>
<label><input type="checkbox" class="highlighted-text-check" value="bgColor"> Font with background color &nbsp;</label>
</div>
</div>`; </div>`;
export default class HighlightedTextOptions extends OptionsWidget { export default class HighlightedTextOptions extends OptionsWidget {
@ -18,20 +20,20 @@ export default class HighlightedTextOptions extends OptionsWidget {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$hlt = this.$widget.find("input.highlighted-text-check"); this.$hlt = this.$widget.find("input.highlighted-text-check");
this.$hlt.on('change', () => { this.$hlt.on('change', () => {
const hltVals=this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function() { const hltVals = this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function () {
return this.value; return this.value;
}).get(); }).get();
this.updateOption('highlightedText', JSON.stringify(hltVals)); this.updateOption('highlightedText', JSON.stringify(hltVals));
}); });
} }
async optionsLoaded(options) { async optionsLoaded(options) {
const hltVals=JSON.parse(options.highlightedText); const hltVals = JSON.parse(options.highlightedText);
this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () { this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () {
if ($.inArray($(this).val(), hltVals) !== -1) { if ($.inArray($(this).val(), hltVals) !== -1) {
$(this).prop("checked", true); $(this).prop("checked", true);
} else { } else {
$(this).prop("checked", false); $(this).prop("checked", false);
} }
}); });
} }