Do not underline inline LaTeX, inline code, images
All checks were successful
Build on Push and create Release on Tag / build (push) Successful in 4m7s

This commit is contained in:
MassiveBox 2025-10-19 10:48:45 +02:00
parent 0442ac2bac
commit 8fcc1c76e9
Signed by: massivebox
GPG key ID: 9B74D3A59181947D
3 changed files with 47 additions and 13 deletions

View file

@ -41,6 +41,28 @@ export class ProtyleHelper {
return document.querySelector(`div.underline-overlay[for-block-id="${blockID}"]`) return document.querySelector(`div.underline-overlay[for-block-id="${blockID}"]`)
} }
public static getElementAtTextIndex(root: Element, index: number): Node {
let currentOffset = 0;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
while (walker.nextNode()) {
let node = walker.currentNode
const textLength = node.textContent.length;
if (currentOffset + textLength >= index) {
let parent: Element = node.parentElement;
while (parent && parent != root) {
node = parent
parent = node.parentElement
}
return node; // The element containing this text
}
currentOffset += textLength;
}
return null;
}
// given an element such as a span inside a block, return its blockID // given an element such as a span inside a block, return its blockID
public static getNodeId(el: Element) { public static getNodeId(el: Element) {
let i = 0; let i = 0;

View file

@ -49,7 +49,7 @@ export class SpellCheckerUI {
// Find the text nodes and character positions // Find the text nodes and character positions
const range = this.createRangeFromCharacterIndices(startIndex, endIndex); const range = this.createRangeFromCharacterIndices(startIndex, endIndex);
if (range) { if (range) {
this.createUnderlineFromRange(range, endIndex - startIndex); this.createUnderlineFromRange(range);
} }
} }
@ -101,7 +101,7 @@ export class SpellCheckerUI {
return null; return null;
} }
private createUnderlineFromRange(range: Range, charsCount: number) { private createUnderlineFromRange(range: Range) {
const rects = range.getClientRects(); const rects = range.getClientRects();
const editorRect = this.block.getBoundingClientRect(); const editorRect = this.block.getBoundingClientRect();
@ -120,19 +120,10 @@ export class SpellCheckerUI {
underline.style.top = (top + 2 + offset.v) + 'px'; underline.style.top = (top + 2 + offset.v) + 'px';
underline.style.width = width + 'px'; underline.style.width = width + 'px';
if(!SpellCheckerUI.checkDontUnderline(width, charsCount)) { this.overlay.appendChild(underline);
this.overlay.appendChild(underline);
}
} }
} }
// if the underline is too wide for the number of characters that are underlined, we don't render it
// this is a consequence of using .innerText: things like <img> tags are only a character
private static checkDontUnderline(width: number, charsCount: number) {
const maxWidthPerChar = 16;
return width > maxWidthPerChar * charsCount
}
private static distance(elA: HTMLElement, elB: HTMLElement): {h: number, v: number} { private static distance(elA: HTMLElement, elB: HTMLElement): {h: number, v: number} {
const rectA = elA.getBoundingClientRect(); const rectA = elA.getBoundingClientRect();
const rectB = elB.getBoundingClientRect(); const rectB = elB.getBoundingClientRect();

View file

@ -20,6 +20,12 @@ export class SuggestionEngine {
private blockStorage: BlockStorage = {}; private blockStorage: BlockStorage = {};
private plugin: SpellCheckPlugin; private plugin: SpellCheckPlugin;
private static blacklisted: string[] = [
"span[data-type='inline-math']",
"span[data-type='img']",
"span[data-type='code']"
];
constructor(plugin: SpellCheckPlugin) { constructor(plugin: SpellCheckPlugin) {
this.plugin = plugin this.plugin = plugin
} }
@ -127,7 +133,8 @@ export class SuggestionEngine {
thisBlock.spellChecker.clearUnderlines() thisBlock.spellChecker.clearUnderlines()
thisBlock.suggestions?.forEach(suggestion => { thisBlock.suggestions?.forEach(suggestion => {
if(!Settings.isInCustomDictionary(this.suggestionToWrongText(suggestion, blockID), this.plugin.settingsUtil)) { if(this.shouldSuggest(blockID, thisBlock, suggestion) &&
!Settings.isInCustomDictionary(this.suggestionToWrongText(suggestion, blockID), this.plugin.settingsUtil)) {
try { try {
thisBlock.spellChecker.highlightCharacterRange(suggestion.offset, suggestion.offset + suggestion.length) thisBlock.spellChecker.highlightCharacterRange(suggestion.offset, suggestion.offset + suggestion.length)
}catch (_) { }catch (_) {
@ -138,6 +145,20 @@ export class SuggestionEngine {
} }
private shouldSuggest(blockID: string, block: StoredBlock, suggestion: Suggestion): boolean {
const element = block.protyle.fastGetBlockElement(blockID)
const eai = ProtyleHelper.getElementAtTextIndex(element, suggestion.offset + suggestion.length)
for(let blacklisted of SuggestionEngine.blacklisted) {
if(eai instanceof Element && eai.matches(blacklisted)) {
return false
}
}
return true
}
public suggestionToWrongText(suggestion: Suggestion, blockID: string): string { public suggestionToWrongText(suggestion: Suggestion, blockID: string): string {
if(!(blockID in this.blockStorage)) { if(!(blockID in this.blockStorage)) {
return return