Skip to content

Commit

Permalink
Merge pull request #416 from SiriusXT/highlightslits
Browse files Browse the repository at this point in the history
Toc and Highlightslist improvement
  • Loading branch information
eliandoran authored Sep 12, 2024
2 parents 8bfa446 + b63b603 commit 93ce81b
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/public/app/layouts/desktop_layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
import MermaidWidget from "../widgets/mermaid.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
Expand Down Expand Up @@ -160,6 +162,8 @@ export default class DesktopLayout {
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";

export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}

constructor() {
super();

this.icon("bx-highlight")
.title(t("show_highlights_list_widget_button.show_highlights_list"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}

async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
}
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'hideHighlightWidget')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}

async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}
50 changes: 50 additions & 0 deletions src/public/app/widgets/buttons/show_toc_widget_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";

export default class ShowTocWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}

constructor() {
super();

this.icon("bx-objects-horizontal-left")
.title(t("show_toc_widget_button.show_toc"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}

async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
}
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}

async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}
114 changes: 104 additions & 10 deletions src/public/app/widgets/highlights_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 = `<div class="highlights-list-widget">
<style>
Expand Down Expand Up @@ -51,7 +52,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
.icon("bx-cog")
.title("Options")
.titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
.class("icon-action"),
new OnClickButtonWidget()
.icon("bx-x")
Expand Down Expand Up @@ -96,8 +97,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
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} = this.getHighlightList(content, optionsHighlightsList));
const { content } = await note.getNoteComplement();
({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
}
this.$highlightsList.empty().append($highlightsList);
if (hlLiCount > 0) {
Expand All @@ -111,7 +112,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}

getHighlightList(content, optionsHighlightsList) {
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 = /<span class="math-tex">\\\(([\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 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
// matches a span containing color
Expand Down Expand Up @@ -151,6 +224,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
const $highlightsList = $("<ol>");
let prevEndIndex = -1, hlLiCount = 0;
let prevSubHtml = null;
// Used to determine if a string is only a formula
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;

for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
const subHtml = match[0];
const startIndex = match.index;
Expand All @@ -165,11 +242,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
const hasText = $(subHtml).text().trim();

if (hasText) {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
const substring = content.substring(prevEndIndex, startIndex);
//If the two elements have the same style and there are only formulas in between, append the formulas and the current element to the end of the previous element.
if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
const $lastLi = $highlightsList.children('li').last();
$lastLi.append(await this.replaceMathTextWithKatax(substring));
$lastLi.append(subHtml);
} else {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
}

hlLiCount++;
} else {
Expand All @@ -178,6 +263,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
}
}
prevEndIndex = endIndex;
prevSubHtml = subHtml;
}
return {
$highlightsList,
Expand Down Expand Up @@ -231,9 +317,17 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
appContext.triggerEvent("reEvaluateHighlightsListWidgetVisibility", { noteId: this.noteId });
}

async showHighlightsListWidgetEvent({ noteId }) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
}

async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
Expand Down
63 changes: 59 additions & 4 deletions src/public/app/widgets/toc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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 = `<div class="toc-widget">
<style>
Expand Down Expand Up @@ -119,6 +120,52 @@ export default class TocWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}

/**
* 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 = /<span class="math-tex">\\\(([\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;
}

/**
* Builds a jquery table of contents.
*
Expand All @@ -127,7 +174,7 @@ export default class TocWidget extends RightPanelWidget {
* with an onclick event that will cause the document to scroll to
* the desired position.
*/
getToc(html) {
async getToc(html) {
// Regular expression for headings <h1>...</h1> using non-greedy
// matching and backreferences
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
Expand Down Expand Up @@ -166,8 +213,8 @@ export default class TocWidget extends RightPanelWidget {
// Create the list item and set up the click callback
//

const headingText = $("<div>").html(m[2]).text();
const $li = $('<li>').text(headingText);
const headingText = await this.replaceMathTextWithKatax(m[2])
const $li = $('<li>').html(headingText);
$li.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li);
headingCount = headingIndex;
Expand Down Expand Up @@ -227,9 +274,17 @@ export default class TocWidget extends RightPanelWidget {
this.noteContext.viewScope.tocTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
}

async showTocWidgetEvent({ noteId }) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
}

async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
Expand Down
Loading

0 comments on commit 93ce81b

Please sign in to comment.