generated from mirrors/plugin-sample-vite-svelte
Compare commits
No commits in common. "main" and "v0.3.0" have entirely different histories.
8 changed files with 36 additions and 75 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "syspell",
|
"name": "syspell",
|
||||||
"version": "0.4.0",
|
"version": "0.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "This SiYuan plugin adds a fully featured grammar and spell checker, powered by LanguageTool.",
|
"description": "This SiYuan plugin adds a fully featured grammar and spell checker, powered by LanguageTool.",
|
||||||
"repository": "https://git.massive.box/massivebox/syspell",
|
"repository": "https://git.massive.box/massivebox/syspell",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "syspell",
|
"name": "syspell",
|
||||||
"author": "massivebox",
|
"author": "massivebox",
|
||||||
"url": "https://git.massive.box/massivebox/syspell",
|
"url": "https://git.massive.box/massivebox/syspell",
|
||||||
"version": "0.4.0",
|
"version": "0.3.0",
|
||||||
"minAppVersion": "3.0.12",
|
"minAppVersion": "3.0.12",
|
||||||
"backends": [
|
"backends": [
|
||||||
"windows",
|
"windows",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"syspell": "SySpell",
|
|
||||||
"settings":{
|
"settings":{
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Information",
|
"title": "Information",
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export default class SpellCheckPlugin extends Plugin {
|
||||||
this.eventBus.on('ws-main', async (event) => {
|
this.eventBus.on('ws-main', async (event) => {
|
||||||
|
|
||||||
if (event.detail.cmd != 'transactions') {
|
if (event.detail.cmd != 'transactions') {
|
||||||
void this.suggestions.forAllBlocksSuggest(false, true)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
45
src/menus.ts
45
src/menus.ts
|
|
@ -3,7 +3,6 @@ import SpellCheckPlugin from "@/index";
|
||||||
import {getBlockAttrs, setBlockAttrs} from "@/api";
|
import {getBlockAttrs, setBlockAttrs} from "@/api";
|
||||||
import {Settings} from "@/settings";
|
import {Settings} from "@/settings";
|
||||||
import {ProtyleHelper} from "@/protyleHelper";
|
import {ProtyleHelper} from "@/protyleHelper";
|
||||||
import {Analytics} from "@/analytics";
|
|
||||||
|
|
||||||
export class Menus {
|
export class Menus {
|
||||||
|
|
||||||
|
|
@ -68,9 +67,7 @@ export class Menus {
|
||||||
|
|
||||||
public async addSettingsToDocMenu(docID: string, menu: subMenu) {
|
public async addSettingsToDocMenu(docID: string, menu: subMenu) {
|
||||||
|
|
||||||
let submenu = []
|
menu.addItem({
|
||||||
|
|
||||||
submenu.push({
|
|
||||||
icon: 'info',
|
icon: 'info',
|
||||||
label: this.plugin.i18nx.docMenu.documentStatus,
|
label: this.plugin.i18nx.docMenu.documentStatus,
|
||||||
click: async () => {
|
click: async () => {
|
||||||
|
|
@ -89,7 +86,7 @@ export class Menus {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
submenu.push({
|
menu.addItem({
|
||||||
icon: 'toggle',
|
icon: 'toggle',
|
||||||
label: this.plugin.i18nx.docMenu.toggleSpellCheck,
|
label: this.plugin.i18nx.docMenu.toggleSpellCheck,
|
||||||
click: async () => {
|
click: async () => {
|
||||||
|
|
@ -107,17 +104,7 @@ export class Menus {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setLang(lang: string, analytics: Analytics) {
|
menu.addItem({
|
||||||
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',
|
icon: 'language',
|
||||||
label: this.plugin.i18nx.docMenu.setDocumentLanguage,
|
label: this.plugin.i18nx.docMenu.setDocumentLanguage,
|
||||||
click: async (_, ev: MouseEvent) => {
|
click: async (_, ev: MouseEvent) => {
|
||||||
|
|
@ -127,13 +114,29 @@ export class Menus {
|
||||||
langMenu.addItem({
|
langMenu.addItem({
|
||||||
icon: 'autodetect',
|
icon: 'autodetect',
|
||||||
label: this.plugin.i18nx.docMenu.autodetectLanguage,
|
label: this.plugin.i18nx.docMenu.autodetectLanguage,
|
||||||
click: async () => setLang('auto', this.plugin.analytics)
|
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()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
languages.forEach(language => {
|
languages.forEach(language => {
|
||||||
langMenu.addItem({
|
langMenu.addItem({
|
||||||
icon: 'language',
|
icon: 'language',
|
||||||
label: language.name + ' [' + language.longCode + ']',
|
label: language.name + ' [' + language.longCode + ']',
|
||||||
click: async () => setLang(language.longCode, this.plugin.analytics)
|
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()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
langMenu.open({ x: ev.clientX, y: ev.clientY });
|
langMenu.open({ x: ev.clientX, y: ev.clientY });
|
||||||
|
|
@ -141,12 +144,6 @@ export class Menus {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.addItem({
|
|
||||||
icon: 'spellcheck',
|
|
||||||
label: this.plugin.i18nx.syspell,
|
|
||||||
submenu: submenu
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -41,28 +41,6 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
this.createUnderlineFromRange(range, endIndex - startIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ export class SpellCheckerUI {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createUnderlineFromRange(range: Range) {
|
private createUnderlineFromRange(range: Range, charsCount: number) {
|
||||||
const rects = range.getClientRects();
|
const rects = range.getClientRects();
|
||||||
const editorRect = this.block.getBoundingClientRect();
|
const editorRect = this.block.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
@ -120,9 +120,18 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -133,8 +127,7 @@ export class SuggestionEngine {
|
||||||
thisBlock.spellChecker.clearUnderlines()
|
thisBlock.spellChecker.clearUnderlines()
|
||||||
|
|
||||||
thisBlock.suggestions?.forEach(suggestion => {
|
thisBlock.suggestions?.forEach(suggestion => {
|
||||||
if(this.shouldSuggest(blockID, thisBlock, suggestion) &&
|
if(!Settings.isInCustomDictionary(this.suggestionToWrongText(suggestion, blockID), this.plugin.settingsUtil)) {
|
||||||
!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 (_) {
|
||||||
|
|
@ -145,20 +138,6 @@ 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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue