generated from mirrors/plugin-sample-vite-svelte
Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 338ac8594e | |||
| 0e392c9dea | |||
| f27227b3a0 | |||
| 8fcc1c76e9 |
8 changed files with 75 additions and 36 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "syspell",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"type": "module",
|
||||
"description": "This SiYuan plugin adds a fully featured grammar and spell checker, powered by LanguageTool.",
|
||||
"repository": "https://git.massive.box/massivebox/syspell",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "syspell",
|
||||
"author": "massivebox",
|
||||
"url": "https://git.massive.box/massivebox/syspell",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"minAppVersion": "3.0.12",
|
||||
"backends": [
|
||||
"windows",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"syspell": "SySpell",
|
||||
"settings":{
|
||||
"info": {
|
||||
"title": "Information",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export default class SpellCheckPlugin extends Plugin {
|
|||
this.eventBus.on('ws-main', async (event) => {
|
||||
|
||||
if (event.detail.cmd != 'transactions') {
|
||||
void this.suggestions.forAllBlocksSuggest(false, true)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
45
src/menus.ts
45
src/menus.ts
|
|
@ -3,6 +3,7 @@ import SpellCheckPlugin from "@/index";
|
|||
import {getBlockAttrs, setBlockAttrs} from "@/api";
|
||||
import {Settings} from "@/settings";
|
||||
import {ProtyleHelper} from "@/protyleHelper";
|
||||
import {Analytics} from "@/analytics";
|
||||
|
||||
export class Menus {
|
||||
|
||||
|
|
@ -67,7 +68,9 @@ export class Menus {
|
|||
|
||||
public async addSettingsToDocMenu(docID: string, menu: subMenu) {
|
||||
|
||||
menu.addItem({
|
||||
let submenu = []
|
||||
|
||||
submenu.push({
|
||||
icon: 'info',
|
||||
label: this.plugin.i18nx.docMenu.documentStatus,
|
||||
click: async () => {
|
||||
|
|
@ -86,7 +89,7 @@ export class Menus {
|
|||
}
|
||||
})
|
||||
|
||||
menu.addItem({
|
||||
submenu.push({
|
||||
icon: 'toggle',
|
||||
label: this.plugin.i18nx.docMenu.toggleSpellCheck,
|
||||
click: async () => {
|
||||
|
|
@ -104,7 +107,17 @@ export class Menus {
|
|||
}
|
||||
})
|
||||
|
||||
menu.addItem({
|
||||
async function setLang(lang: string, analytics: Analytics) {
|
||||
const attrs = await getBlockAttrs(docID)
|
||||
attrs[SpellCheckPlugin.LANGUAGE_ATTR] = lang
|
||||
await setBlockAttrs(docID, attrs)
|
||||
void analytics.sendEvent('docmenu-click-setlang-2', {
|
||||
'language': lang
|
||||
});
|
||||
location.reload()
|
||||
}
|
||||
|
||||
submenu.push({
|
||||
icon: 'language',
|
||||
label: this.plugin.i18nx.docMenu.setDocumentLanguage,
|
||||
click: async (_, ev: MouseEvent) => {
|
||||
|
|
@ -114,29 +127,13 @@ export class Menus {
|
|||
langMenu.addItem({
|
||||
icon: 'autodetect',
|
||||
label: this.plugin.i18nx.docMenu.autodetectLanguage,
|
||||
click: async () => {
|
||||
const attrs = await getBlockAttrs(docID)
|
||||
attrs[SpellCheckPlugin.LANGUAGE_ATTR] = 'auto'
|
||||
await setBlockAttrs(docID, attrs)
|
||||
void this.plugin.analytics.sendEvent('docmenu-click-setlang-2', {
|
||||
'language': 'auto'
|
||||
});
|
||||
location.reload()
|
||||
}
|
||||
click: async () => setLang('auto', this.plugin.analytics)
|
||||
});
|
||||
languages.forEach(language => {
|
||||
langMenu.addItem({
|
||||
icon: 'language',
|
||||
label: language.name + ' [' + language.longCode + ']',
|
||||
click: async () => {
|
||||
const attrs = await getBlockAttrs(docID)
|
||||
attrs[SpellCheckPlugin.LANGUAGE_ATTR] = language.longCode
|
||||
await setBlockAttrs(docID, attrs)
|
||||
void this.plugin.analytics.sendEvent('docmenu-click-setlang-2', {
|
||||
'language': language.longCode
|
||||
});
|
||||
location.reload()
|
||||
}
|
||||
click: async () => setLang(language.longCode, this.plugin.analytics)
|
||||
});
|
||||
});
|
||||
langMenu.open({ x: ev.clientX, y: ev.clientY });
|
||||
|
|
@ -144,6 +141,12 @@ export class Menus {
|
|||
}
|
||||
})
|
||||
|
||||
menu.addItem({
|
||||
icon: 'spellcheck',
|
||||
label: this.plugin.i18nx.syspell,
|
||||
submenu: submenu
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -41,6 +41,28 @@ export class ProtyleHelper {
|
|||
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
|
||||
public static getNodeId(el: Element) {
|
||||
let i = 0;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export class SpellCheckerUI {
|
|||
// Find the text nodes and character positions
|
||||
const range = this.createRangeFromCharacterIndices(startIndex, endIndex);
|
||||
if (range) {
|
||||
this.createUnderlineFromRange(range, endIndex - startIndex);
|
||||
this.createUnderlineFromRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ export class SpellCheckerUI {
|
|||
return null;
|
||||
}
|
||||
|
||||
private createUnderlineFromRange(range: Range, charsCount: number) {
|
||||
private createUnderlineFromRange(range: Range) {
|
||||
const rects = range.getClientRects();
|
||||
const editorRect = this.block.getBoundingClientRect();
|
||||
|
||||
|
|
@ -120,19 +120,10 @@ export class SpellCheckerUI {
|
|||
underline.style.top = (top + 2 + offset.v) + '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} {
|
||||
const rectA = elA.getBoundingClientRect();
|
||||
const rectB = elB.getBoundingClientRect();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ export class SuggestionEngine {
|
|||
private blockStorage: BlockStorage = {};
|
||||
private plugin: SpellCheckPlugin;
|
||||
|
||||
private static blacklisted: string[] = [
|
||||
"span[data-type='inline-math']",
|
||||
"span[data-type='img']",
|
||||
"span[data-type='code']"
|
||||
];
|
||||
|
||||
constructor(plugin: SpellCheckPlugin) {
|
||||
this.plugin = plugin
|
||||
}
|
||||
|
|
@ -127,7 +133,8 @@ export class SuggestionEngine {
|
|||
thisBlock.spellChecker.clearUnderlines()
|
||||
|
||||
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 {
|
||||
thisBlock.spellChecker.highlightCharacterRange(suggestion.offset, suggestion.offset + suggestion.length)
|
||||
}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 {
|
||||
if(!(blockID in this.blockStorage)) {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue