Let toc and highlights_list display formulas

This commit is contained in:
SiriusXT 2024-09-12 09:36:08 +08:00
parent 7a11f9aaff
commit bb97e1a661
2 changed files with 146 additions and 13 deletions

View File

@ -10,6 +10,7 @@ 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";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="highlights-list-widget"> const TPL = `<div class="highlights-list-widget">
<style> <style>
@ -52,7 +53,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
.icon("bx-slider") .icon("bx-slider")
.title("Options") .title("Options")
.titlePlacement("left") .titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true})) .onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
.class("icon-action"), .class("icon-action"),
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-x") .icon("bx-x")
@ -97,8 +98,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
let $highlightsList = "", hlLiCount = -1; let $highlightsList = "", hlLiCount = -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();
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList)); ({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
} }
this.$highlightsList.empty().append($highlightsList); this.$highlightsList.empty().append($highlightsList);
if (hlLiCount > 0) { if (hlLiCount > 0) {
@ -112,7 +113,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility"); 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 // 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
@ -152,6 +225,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
const combinedRegex = new RegExp(combinedRegexStr, 'gi'); const combinedRegex = new RegExp(combinedRegexStr, 'gi');
const $highlightsList = $("<ol>"); const $highlightsList = $("<ol>");
let prevEndIndex = -1, hlLiCount = 0; 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++) { for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
const subHtml = match[0]; const subHtml = match[0];
const startIndex = match.index; const startIndex = match.index;
@ -166,11 +243,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
const hasText = $(subHtml).text().trim(); const hasText = $(subHtml).text().trim();
if (hasText) { if (hasText) {
$highlightsList.append( const substring = content.substring(prevEndIndex, startIndex);
$('<li>') //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.
.html(subHtml) if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex)) 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++; hlLiCount++;
} else { } else {
@ -179,6 +264,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
} }
} }
prevEndIndex = endIndex; prevEndIndex = endIndex;
prevSubHtml = subHtml;
} }
return { return {
$highlightsList, $highlightsList,
@ -234,7 +320,7 @@ export default class HighlightsListWidget 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.getAttributeRows().find(attr => attr.type === 'label' } else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'

View File

@ -19,6 +19,7 @@ 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";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="toc-widget"> const TPL = `<div class="toc-widget">
<style> <style>
@ -120,6 +121,52 @@ export default class TocWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility"); 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. * Builds a jquery table of contents.
* *
@ -128,7 +175,7 @@ export default class TocWidget extends RightPanelWidget {
* with an onclick event that will cause the document to scroll to * with an onclick event that will cause the document to scroll to
* the desired position. * the desired position.
*/ */
getToc(html) { async getToc(html) {
// Regular expression for headings <h1>...</h1> using non-greedy // Regular expression for headings <h1>...</h1> using non-greedy
// matching and backreferences // matching and backreferences
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi; const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
@ -167,8 +214,8 @@ export default class TocWidget extends RightPanelWidget {
// Create the list item and set up the click callback // Create the list item and set up the click callback
// //
const headingText = $("<div>").html(m[2]).text(); const headingText = await this.replaceMathTextWithKatax(m[2])
const $li = $('<li>').text(headingText); const $li = $('<li>').html(headingText);
$li.on("click", () => this.jumpToHeading(headingIndex)); $li.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li); $ols[$ols.length - 1].append($li);
headingCount = headingIndex; headingCount = headingIndex;