on-codemerge
Version:
A WYSIWYG editor for on-codemerge is a user-friendly interface that allows users to edit and view their code in real time, exactly as it will appear in the final product
199 lines (198 loc) • 9.38 kB
JavaScript
var m = Object.defineProperty;
var g = (i, e, t) => e in i ? m(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
var o = (i, e, t) => g(i, typeof e != "symbol" ? e + "" : e, t);
import { createToolbarButton as k } from "../ToolbarPlugin/utils.mjs";
import C from "../../icons/spell-check.svg.mjs";
import x from "../../../node_modules/.pnpm/spellchecker-wasm@0.3.3/node_modules/spellchecker-wasm/lib/spellchecker-wasm.wasm.mjs";
const S = /* @__PURE__ */ Object.assign({
"./dictionaries/ar.txt": () => import("./dictionaries/ar.txt.mjs").then((i) => i.default),
"./dictionaries/cs.txt": () => import("./dictionaries/cs.txt.mjs").then((i) => i.default),
"./dictionaries/de.txt": () => import("./dictionaries/de.txt.mjs").then((i) => i.default),
"./dictionaries/en.txt": () => import("./dictionaries/en.txt.mjs").then((i) => i.default),
"./dictionaries/es.txt": () => import("./dictionaries/es.txt.mjs").then((i) => i.default),
"./dictionaries/fr.txt": () => import("./dictionaries/fr.txt.mjs").then((i) => i.default),
"./dictionaries/hi.txt": () => import("./dictionaries/hi.txt.mjs").then((i) => i.default),
"./dictionaries/id.txt": () => import("./dictionaries/id.txt.mjs").then((i) => i.default),
"./dictionaries/it.txt": () => import("./dictionaries/it.txt.mjs").then((i) => i.default),
"./dictionaries/ja.txt": () => import("./dictionaries/ja.txt.mjs").then((i) => i.default),
"./dictionaries/ko.txt": () => import("./dictionaries/ko.txt.mjs").then((i) => i.default),
"./dictionaries/nl.txt": () => import("./dictionaries/nl.txt.mjs").then((i) => i.default),
"./dictionaries/pl.txt": () => import("./dictionaries/pl.txt.mjs").then((i) => i.default),
"./dictionaries/pt.txt": () => import("./dictionaries/pt.txt.mjs").then((i) => i.default),
"./dictionaries/ru.txt": () => import("./dictionaries/ru.txt.mjs").then((i) => i.default),
"./dictionaries/th.txt": () => import("./dictionaries/th.txt.mjs").then((i) => i.default),
"./dictionaries/tr.txt": () => import("./dictionaries/tr.txt.mjs").then((i) => i.default),
"./dictionaries/vi.txt": () => import("./dictionaries/vi.txt.mjs").then((i) => i.default),
"./dictionaries/zh.txt": () => import("./dictionaries/zh.txt.mjs").then((i) => i.default)
});
class v {
constructor() {
o(this, "name", "spellchecker");
o(this, "hotkeys", [
{
keys: "Ctrl+Shift+L",
description: "Run spell checker",
command: "spellchecker",
icon: "🔍✅"
}
]);
o(this, "editor", null);
o(this, "toolbarButton", null);
o(this, "isSpellCheckEnabled", !1);
o(this, "spellChecker", null);
o(this, "misspelledWords", /* @__PURE__ */ new Map());
o(this, "lastResults", []);
}
async initialize(e) {
this.editor = e, this.addToolbarButton(), this.enableSpellCheck(this.isSpellCheckEnabled), this.editor.on("spellchecker", async () => {
var t, l;
this.isSpellCheckEnabled = !this.isSpellCheckEnabled, this.isSpellCheckEnabled && await this.loadDictionary(((t = this.editor) == null ? void 0 : t.getLocale()) ?? "en"), this.enableSpellCheck(this.isSpellCheckEnabled), (l = this.toolbarButton) == null || l.classList.toggle("active", this.isSpellCheckEnabled);
});
}
async loadDictionary(e) {
try {
const t = await fetch(x), l = S[`./dictionaries/${e}.txt`];
if (!l)
throw new Error(`Dictionary files for locale ${e} not found`);
const s = await l(), n = await fetch(s);
if (!t.ok || !n.ok)
throw new Error("Failed to load dictionary files");
const { SpellcheckerWasm: a } = await import("../../../_virtual/index.mjs").then((r) => r.i);
this.spellChecker = new a((r) => {
this.lastResults = r;
}), await this.spellChecker.prepareSpellchecker(t, n);
} catch (t) {
console.error("Failed to load dictionary:", t), this.spellChecker = null;
}
}
addToolbarButton() {
var t;
const e = document.querySelector(".editor-toolbar");
e && (this.toolbarButton = k({
icon: C,
title: (t = this.editor) == null ? void 0 : t.t("Spell Check"),
onClick: async () => {
var l, s;
this.isSpellCheckEnabled = !this.isSpellCheckEnabled, this.isSpellCheckEnabled && await this.loadDictionary(((l = this.editor) == null ? void 0 : l.getLocale()) ?? "en"), this.enableSpellCheck(this.isSpellCheckEnabled), (s = this.toolbarButton) == null || s.classList.toggle("active", this.isSpellCheckEnabled);
}
}), e.appendChild(this.toolbarButton));
}
enableSpellCheck(e) {
var l;
((l = this.editor) == null ? void 0 : l.getContainer()) && (e ? (this.addSpellCheckListeners(), this.checkAllText()) : (this.removeSpellCheckListeners(), this.clearHighlights()));
}
addSpellCheckListeners() {
var t;
const e = (t = this.editor) == null ? void 0 : t.getContainer();
e && (e.addEventListener("input", this.handleInput.bind(this)), e.addEventListener("mouseup", this.handleMouseUp.bind(this)), e.addEventListener("contextmenu", this.handleContextMenu.bind(this)));
}
removeSpellCheckListeners() {
var t;
const e = (t = this.editor) == null ? void 0 : t.getContainer();
e && (e.removeEventListener("input", this.handleInput.bind(this)), e.removeEventListener("mouseup", this.handleMouseUp.bind(this)), e.removeEventListener("contextmenu", this.handleContextMenu.bind(this)));
}
handleInput(e) {
this.checkAllText();
}
handleMouseUp(e) {
const t = window.getSelection();
if (t && t.rangeCount > 0) {
const l = t.getRangeAt(0);
l.toString() && this.checkSpelling(l);
}
}
handleContextMenu(e) {
const t = window.getSelection();
if (t && t.rangeCount > 0) {
const l = t.getRangeAt(0), s = l.toString();
if (s && this.isMisspelled(s)) {
e.preventDefault();
const n = this.getSpellingSuggestions(s);
n.length > 0 && this.showContextMenu(e, n, l);
}
}
}
checkAllText() {
var s;
const e = (s = this.editor) == null ? void 0 : s.getContainer();
if (!e) return;
this.clearHighlights();
const t = document.createTreeWalker(e, NodeFilter.SHOW_TEXT);
let l;
for (; l = t.nextNode(); ) {
const n = l, a = n.textContent;
if (a) {
const r = a.split(/\s+/);
let d = 0;
r.forEach((h) => {
if (this.isMisspelled(h)) {
const c = document.createRange();
c.setStart(n, d), c.setEnd(n, d + h.length), this.highlightMisspelledWord(n, c);
}
d += h.length + 1;
});
}
}
}
checkSpelling(e) {
const t = e.toString();
if (this.isMisspelled(t)) {
const l = e.startContainer;
this.highlightMisspelledWord(l, e);
}
}
isMisspelled(e) {
return this.spellChecker ? (this.lastResults = [], this.spellChecker.checkSpelling(e, {
includeUnknown: !0,
// Включаем слова, которых нет в словаре
maxEditDistance: 2,
// Максимальное расстояние редактирования
verbosity: 1,
// Уровень детализации предложений
includeSelf: !1
// Не включать само слово в результаты
}), this.lastResults.length > 0) : !1;
}
getSpellingSuggestions(e) {
return this.spellChecker ? (this.lastResults = [], this.spellChecker.checkSpelling(e, {
includeUnknown: !0,
maxEditDistance: 2,
verbosity: 2,
// Возвращаем все предложения
includeSelf: !1
}), this.lastResults.map((t) => t.term)) : [];
}
highlightMisspelledWord(e, t) {
var l;
(l = e.parentElement) == null || l.classList.add("misspelled-word"), this.misspelledWords.set(e, t);
}
clearHighlights() {
this.misspelledWords.forEach((e, t) => {
var l;
(l = t.parentElement) == null || l.classList.remove("misspelled-word");
}), this.misspelledWords.clear();
}
showContextMenu(e, t, l) {
var u;
const s = document.createElement("div");
s.style.position = "fixed", s.style.backgroundColor = "#fff", s.style.border = "1px solid #ccc", s.style.boxShadow = "0 2px 5px rgba(0, 0, 0, 0.2)", s.style.zIndex = "1000", s.style.maxHeight = "200px", s.style.overflowY = "auto", s.style.padding = "4px 0";
const n = 200, a = Math.min(t.length * 32, 200), r = window.innerWidth, d = window.innerHeight;
let h = e.clientY, c = e.clientX;
h + a > d && (h = d - a), c + n > r && (c = r - n), s.style.top = `${h}px`, s.style.left = `${c}px`, t.forEach((f) => {
const p = document.createElement("div");
p.style.padding = "8px 16px", p.style.cursor = "pointer", p.textContent = f, p.addEventListener("click", () => {
this.replaceMisspelledWord(l, f), s.remove();
}), s.appendChild(p);
}), (u = this.editor) == null || u.getInnerContainer().appendChild(s), document.addEventListener("click", () => s.remove(), { once: !0 });
}
replaceMisspelledWord(e, t) {
e.deleteContents(), e.insertNode(document.createTextNode(t)), this.checkAllText();
}
destroy() {
var e;
this.enableSpellCheck(!1), (e = this.toolbarButton) == null || e.remove(), this.toolbarButton = null, this.editor = null, this.spellChecker = null;
}
}
export {
v as SpellCheckerPlugin
};