@editora/spell-check 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +66 -0
- package/dist/index.esm.js +458 -0
- package/package.json +35 -0
- package/src/SpellCheckPlugin.native.ts +1423 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const N=new Set(["the","a","an","and","or","but","in","on","at","to","for","of","with","by","from","is","are","be","was","were","have","has","had","do","does","did","will","would","could","should","may","might","must","can","this","that","these","those","what","which","who","whom","where","when","why","how","all","each","every","both","few","more","most","other","same","such","no","nor","not","only","own","so","than","too","very","just","as","if","because","while","although","though","it","its","their","them","they","you","he","she","we","me","him","her","us","our","i","my","your","his","hers","ours","yours","theirs","editor","document","text","word","paragraph","line","page","content","hello","world","test","example","sample","demo","lorem","ipsum"]),x=new Set,y=new Set;let f=!1,p=null,g=null,l=null;const z=()=>{try{const t=localStorage.getItem("rte-custom-dictionary");t&&JSON.parse(t).forEach(e=>x.add(e.toLowerCase()))}catch(t){console.warn("Failed to load custom dictionary:",t)}},D=()=>{try{const t=Array.from(x);localStorage.setItem("rte-custom-dictionary",JSON.stringify(t))}catch(t){console.warn("Failed to save custom dictionary:",t)}};function F(t,n){const e=[];for(let o=0;o<=n.length;o++)e[o]=[o];for(let o=0;o<=t.length;o++)e[0][o]=o;for(let o=1;o<=n.length;o++)for(let s=1;s<=t.length;s++)n.charAt(o-1)===t.charAt(s-1)?e[o][s]=e[o-1][s-1]:e[o][s]=Math.min(e[o-1][s-1]+1,e[o][s-1]+1,e[o-1][s]+1);return e[n.length][t.length]}function $(t){const n=t.toLowerCase();return N.has(n)||x.has(n)||y.has(n)}function I(t,n=5){const e=t.toLowerCase(),s=Array.from(N).map(r=>({word:r,distance:F(e,r)}));return s.sort((r,i)=>r.distance-i.distance),s.filter(r=>r.distance<=3).slice(0,n).map(r=>r.word)}function j(t){if(t.nodeType!==Node.ELEMENT_NODE)return!1;const n=t;return!!(n.closest('code, pre, [contenteditable="false"], .rte-widget, .rte-template, .rte-comment, .rte-merge-tag')||n.hasAttribute("data-comment-id")||n.hasAttribute("data-template")||n.hasAttribute("data-merge-tag"))}function R(t){const n=[],e=/([\p{L}\p{M}\p{N}\p{Emoji_Presentation}\u200d'-]+|[\uD800-\uDBFF][\uDC00-\uDFFF])/gu;let o;for(;(o=e.exec(t.data))!==null;){const s=o[0],r=o.index,i=r+s.length;/https?:\/\//.test(s)||/@/.test(s)||/\{\{.*\}\}/.test(s)||/^\d+$/.test(s)||$(s)||/[a-z][A-Z]/.test(s)||/-/.test(s)||s[0]===s[0].toUpperCase()&&s.length>1||n.push({id:`${s}-${r}`,node:t,startOffset:r,endOffset:i,word:s,suggestions:I(s),ignored:!1})}return n}const b=()=>{const t=window.getSelection();if(t&&t.rangeCount>0){let e=t.getRangeAt(0).startContainer;for(;e&&e!==document.body;){if(e.nodeType===Node.ELEMENT_NODE){const o=e;if(o.getAttribute("contenteditable")==="true")return o}e=e.parentNode}}const n=document.activeElement;if(n){if(n.getAttribute("contenteditable")==="true")return n;const e=n.closest('[contenteditable="true"]');if(e)return e}return document.querySelector('[contenteditable="true"]')};function v(){const t=b();if(!t)return[];const n=[],e=document.createTreeWalker(t,NodeFilter.SHOW_TEXT,{acceptNode:s=>!s.textContent?.trim()||s.parentNode&&j(s.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT});let o=e.nextNode();for(;o;)n.push(...R(o)),o=e.nextNode();return n}function w(t){const n=b();n&&(t||(t=v()),n.querySelectorAll(".rte-misspelled").forEach(e=>{const o=e.parentNode;if(o){for(;e.firstChild;)o.insertBefore(e.firstChild,e);o.removeChild(e)}}),t.forEach(e=>{if(y.has(e.word.toLowerCase()))return;const o=e.node.data.length;if(!(e.startOffset<0||e.endOffset>o||e.startOffset>=e.endOffset))try{const s=document.createRange();s.setStart(e.node,e.startOffset),s.setEnd(e.node,e.endOffset);const r=document.createElement("span");r.className="rte-misspelled",r.setAttribute("data-word",e.word),r.setAttribute("data-suggestions",e.suggestions.join(",")),r.setAttribute("title",`Suggestions: ${e.suggestions.join(", ")}`),r.style.borderBottom="2px wavy red",r.style.cursor="pointer",s.surroundContents(r)}catch{}}),m())}function C(){const t=document.querySelector('[contenteditable="true"]');t&&t.querySelectorAll(".rte-misspelled").forEach(n=>{const e=n.parentNode;if(e){for(;n.firstChild;)e.insertBefore(n.firstChild,n);e.removeChild(n)}})}function A(t,n){const e=document.createRange();e.setStart(t.node,t.startOffset),e.setEnd(t.node,t.endOffset);const o=document.createTextNode(n);e.deleteContents(),e.insertNode(o)}function T(t){y.add(t.toLowerCase()),C(),w(),m()}function L(t){x.add(t.toLowerCase()),D(),C(),w(),m()}function q(t){const n=b();if(!n)return{total:0,misspelled:0,accuracy:100};t||(t=v());const e=t.filter(i=>!y.has(i.word.toLowerCase())).length,r=((n.textContent||"").match(/[\p{L}\p{M}\p{N}]+/gu)||[]).length;return{total:r,misspelled:e,accuracy:r>0?(r-e)/r*100:100}}function _(t,n,e,o,s){document.querySelectorAll(".rte-spellcheck-menu").forEach(a=>a.remove());const r=document.createElement("div");if(r.className="rte-spellcheck-menu",r.style.cssText=`
|
|
2
|
+
position: fixed;
|
|
3
|
+
left: ${t}px;
|
|
4
|
+
top: ${n}px;
|
|
5
|
+
background: #fff;
|
|
6
|
+
border: 1px solid #ccc;
|
|
7
|
+
border-radius: 4px;
|
|
8
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
9
|
+
z-index: 99999;
|
|
10
|
+
padding: 4px 0;
|
|
11
|
+
min-width: 160px;
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
13
|
+
font-size: 13px;
|
|
14
|
+
`,o.slice(0,5).forEach(a=>{const u=document.createElement("div");u.className="rte-spellcheck-menu-item",u.textContent=a,u.style.cssText="padding: 6px 16px; cursor: pointer; transition: background 0.2s;",u.onmouseenter=()=>u.style.backgroundColor="#f0f0f0",u.onmouseleave=()=>u.style.backgroundColor="transparent",u.onclick=()=>{const E=s.parentNode;if(E){for(const h of E.childNodes)if(h.nodeType===Node.TEXT_NODE&&h.textContent?.includes(e)){const S=h.textContent.indexOf(e),M={node:h,startOffset:S,endOffset:S+e.length};A(M,a);break}}r.remove()},r.appendChild(u)}),o.length>0){const a=document.createElement("div");a.style.cssText="height: 1px; background: #ddd; margin: 4px 0;",r.appendChild(a)}const i=document.createElement("div");i.className="rte-spellcheck-menu-item",i.textContent="Ignore Once",i.style.cssText="padding: 6px 16px; cursor: pointer; color: #666;",i.onmouseenter=()=>i.style.backgroundColor="#f0f0f0",i.onmouseleave=()=>i.style.backgroundColor="transparent",i.onclick=()=>{s.remove(),r.remove()},r.appendChild(i);const d=document.createElement("div");d.className="rte-spellcheck-menu-item",d.textContent="Ignore All",d.style.cssText="padding: 6px 16px; cursor: pointer; color: #666;",d.onmouseenter=()=>d.style.backgroundColor="#f0f0f0",d.onmouseleave=()=>d.style.backgroundColor="transparent",d.onclick=()=>{T(e),r.remove()},r.appendChild(d);const c=document.createElement("div");c.className="rte-spellcheck-menu-item",c.textContent="Add to Dictionary",c.style.cssText="padding: 6px 16px; cursor: pointer; color: #1976d2;",c.onmouseenter=()=>c.style.backgroundColor="#f0f0f0",c.onmouseleave=()=>c.style.backgroundColor="transparent",c.onclick=()=>{L(e),r.remove()},r.appendChild(c),document.body.appendChild(r);const k=a=>{r.contains(a.target)||(r.remove(),document.removeEventListener("mousedown",k))};setTimeout(()=>document.addEventListener("mousedown",k),0)}function B(){document.addEventListener("contextmenu",t=>{const n=t.target;if(n&&n.classList.contains("rte-misspelled")){t.preventDefault();const e=n.getAttribute("data-word"),o=(n.getAttribute("data-suggestions")||"").split(",").filter(s=>s);_(t.clientX,t.clientY,e,o,n)}})}function H(){const t=document.createElement("div");return t.className="rte-spell-check-panel",t.style.cssText=`
|
|
15
|
+
position: fixed;
|
|
16
|
+
right: 0;
|
|
17
|
+
top: 0;
|
|
18
|
+
width: 350px;
|
|
19
|
+
height: 100vh;
|
|
20
|
+
background: white;
|
|
21
|
+
border-left: 1px solid #ddd;
|
|
22
|
+
box-shadow: -2px 0 4px rgba(0,0,0,0.1);
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
z-index: 10000;
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
26
|
+
padding: 16px;
|
|
27
|
+
`,document.body.appendChild(t),t}function m(){if(!l)return;const t=v(),n=q(t);l.innerHTML=`
|
|
28
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
29
|
+
<h3 style="margin: 0; font-size: 16px; font-weight: 600;">Spell Check</h3>
|
|
30
|
+
<button class="rte-spellcheck-close" style="background: none; border: none; font-size: 22px; cursor: pointer; color: #888;">✕</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div style="display: flex; gap: 24px; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid #eee;">
|
|
34
|
+
<div>
|
|
35
|
+
<div style="font-size: 12px; color: #999;">Total Words</div>
|
|
36
|
+
<div style="font-size: 16px; font-weight: 600; color: #333;">${n.total}</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<div style="font-size: 12px; color: #999;">Misspelled</div>
|
|
40
|
+
<div style="font-size: 16px; font-weight: 600; color: #d32f2f;">${n.misspelled}</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div>
|
|
43
|
+
<div style="font-size: 12px; color: #999;">Accuracy</div>
|
|
44
|
+
<div style="font-size: 16px; font-weight: 600; color: #388e3c;">${n.accuracy.toFixed(1)}%</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="misspellings-list">
|
|
49
|
+
${t.length===0?'<div style="padding: 12px; text-align: center; color: #999; font-size: 13px;">No spelling errors found</div>':t.map((o,s)=>`
|
|
50
|
+
<div class="misspelling-item" data-word="${o.word}" data-index="${s}" style="padding: 8px; margin-bottom: 8px; background-color: #f5f5f5; border-radius: 4px;">
|
|
51
|
+
<div class="word-header" style="display: flex; justify-content: space-between; align-items: center; cursor: pointer;">
|
|
52
|
+
<span style="font-weight: 600; color: #d32f2f;">${o.word}</span>
|
|
53
|
+
<button class="expand-btn" style="background: none; border: none; cursor: pointer; font-size: 12px; color: #666;">▶</button>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="suggestions" style="display: none; margin-top: 8px; padding-top: 8px; border-top: 1px solid #ddd;">
|
|
56
|
+
${o.suggestions.length>0?`<div style="font-size: 12px; font-weight: 600; color: #333; margin-bottom: 6px;">Suggestions:</div>
|
|
57
|
+
${o.suggestions.map(r=>`<button class="suggestion-btn" data-suggestion="${r}" style="display: inline-block; margin-right: 6px; margin-bottom: 6px; padding: 4px 8px; background-color: #1976d2; color: white; border: none; border-radius: 3px; font-size: 12px; cursor: pointer;">${r}</button>`).join("")}`:'<div style="font-size: 12px; color: #999; margin-bottom: 8px;">No suggestions</div>'}
|
|
58
|
+
<div style="margin-top: 8px; display: flex; gap: 6px;">
|
|
59
|
+
<button class="ignore-btn" style="font-size: 12px; padding: 4px 8px; background-color: white; border: 1px solid #ddd; border-radius: 3px; cursor: pointer;">Ignore</button>
|
|
60
|
+
<button class="add-btn" style="font-size: 12px; padding: 4px 8px; background-color: white; border: 1px solid #ddd; border-radius: 3px; cursor: pointer;">Add to Dictionary</button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
`).join("")}
|
|
65
|
+
</div>
|
|
66
|
+
`,l.querySelector(".rte-spellcheck-close")?.addEventListener("click",()=>{O()}),l.querySelectorAll(".word-header").forEach((o,s)=>{o.addEventListener("click",()=>{const i=o.closest(".misspelling-item")?.querySelector(".suggestions"),d=o.querySelector(".expand-btn");i&&d&&(i.style.display==="none"?(i.style.display="block",d.textContent="▼"):(i.style.display="none",d.textContent="▶"))})}),l.querySelectorAll(".suggestion-btn").forEach(o=>{o.addEventListener("click",()=>{const s=o.getAttribute("data-suggestion"),r=o.closest(".misspelling-item");r?.getAttribute("data-word");const i=parseInt(r?.getAttribute("data-index")||"0");t[i]&&A(t[i],s)})}),l.querySelectorAll(".ignore-btn").forEach(o=>{o.addEventListener("click",()=>{const r=o.closest(".misspelling-item")?.getAttribute("data-word");T(r)})}),l.querySelectorAll(".add-btn").forEach(o=>{o.addEventListener("click",()=>{const r=o.closest(".misspelling-item")?.getAttribute("data-word");L(r)})})}function P(){const t=b();t&&(p&&p.disconnect(),p=new MutationObserver(n=>{n.some(e=>e.type==="characterData"||e.type==="childList")&&(g&&clearTimeout(g),g=window.setTimeout(()=>{f&&(w(),m())},350))}),p.observe(t,{characterData:!0,childList:!0,subtree:!0}))}function W(){p&&(p.disconnect(),p=null),g&&(clearTimeout(g),g=null)}function O(){return f=!f,f?(w(),B(),P(),l||(l=H(),m())):(C(),W(),l&&(l.remove(),l=null)),f}const V=()=>({name:"spellCheck",init:()=>{z()},toolbar:[{label:"Spell Check",command:"toggleSpellCheck",icon:'<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 12.5L3.84375 9.5M3.84375 9.5L5 5.38889C5 5.38889 5.25 4.5 6 4.5C6.75 4.5 7 5.38889 7 5.38889L8.15625 9.5M3.84375 9.5H8.15625M9 12.5L8.15625 9.5M13 16.8333L15.4615 19.5L21 13.5M12 8.5H15C16.1046 8.5 17 7.60457 17 6.5C17 5.39543 16.1046 4.5 15 4.5H12V8.5ZM12 8.5H16C17.1046 8.5 18 9.39543 18 10.5C18 11.6046 17.1046 12.5 16 12.5H12V8.5Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',shortcut:"F7"}],commands:{toggleSpellCheck:()=>(O(),!0)},keymap:{F7:"toggleSpellCheck"}});exports.SpellCheckPlugin=V;
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
const N = /* @__PURE__ */ new Set([
|
|
2
|
+
"the",
|
|
3
|
+
"a",
|
|
4
|
+
"an",
|
|
5
|
+
"and",
|
|
6
|
+
"or",
|
|
7
|
+
"but",
|
|
8
|
+
"in",
|
|
9
|
+
"on",
|
|
10
|
+
"at",
|
|
11
|
+
"to",
|
|
12
|
+
"for",
|
|
13
|
+
"of",
|
|
14
|
+
"with",
|
|
15
|
+
"by",
|
|
16
|
+
"from",
|
|
17
|
+
"is",
|
|
18
|
+
"are",
|
|
19
|
+
"be",
|
|
20
|
+
"was",
|
|
21
|
+
"were",
|
|
22
|
+
"have",
|
|
23
|
+
"has",
|
|
24
|
+
"had",
|
|
25
|
+
"do",
|
|
26
|
+
"does",
|
|
27
|
+
"did",
|
|
28
|
+
"will",
|
|
29
|
+
"would",
|
|
30
|
+
"could",
|
|
31
|
+
"should",
|
|
32
|
+
"may",
|
|
33
|
+
"might",
|
|
34
|
+
"must",
|
|
35
|
+
"can",
|
|
36
|
+
"this",
|
|
37
|
+
"that",
|
|
38
|
+
"these",
|
|
39
|
+
"those",
|
|
40
|
+
"what",
|
|
41
|
+
"which",
|
|
42
|
+
"who",
|
|
43
|
+
"whom",
|
|
44
|
+
"where",
|
|
45
|
+
"when",
|
|
46
|
+
"why",
|
|
47
|
+
"how",
|
|
48
|
+
"all",
|
|
49
|
+
"each",
|
|
50
|
+
"every",
|
|
51
|
+
"both",
|
|
52
|
+
"few",
|
|
53
|
+
"more",
|
|
54
|
+
"most",
|
|
55
|
+
"other",
|
|
56
|
+
"same",
|
|
57
|
+
"such",
|
|
58
|
+
"no",
|
|
59
|
+
"nor",
|
|
60
|
+
"not",
|
|
61
|
+
"only",
|
|
62
|
+
"own",
|
|
63
|
+
"so",
|
|
64
|
+
"than",
|
|
65
|
+
"too",
|
|
66
|
+
"very",
|
|
67
|
+
"just",
|
|
68
|
+
"as",
|
|
69
|
+
"if",
|
|
70
|
+
"because",
|
|
71
|
+
"while",
|
|
72
|
+
"although",
|
|
73
|
+
"though",
|
|
74
|
+
"it",
|
|
75
|
+
"its",
|
|
76
|
+
"their",
|
|
77
|
+
"them",
|
|
78
|
+
"they",
|
|
79
|
+
"you",
|
|
80
|
+
"he",
|
|
81
|
+
"she",
|
|
82
|
+
"we",
|
|
83
|
+
"me",
|
|
84
|
+
"him",
|
|
85
|
+
"her",
|
|
86
|
+
"us",
|
|
87
|
+
"our",
|
|
88
|
+
"i",
|
|
89
|
+
"my",
|
|
90
|
+
"your",
|
|
91
|
+
"his",
|
|
92
|
+
"hers",
|
|
93
|
+
"ours",
|
|
94
|
+
"yours",
|
|
95
|
+
"theirs",
|
|
96
|
+
"editor",
|
|
97
|
+
"document",
|
|
98
|
+
"text",
|
|
99
|
+
"word",
|
|
100
|
+
"paragraph",
|
|
101
|
+
"line",
|
|
102
|
+
"page",
|
|
103
|
+
"content",
|
|
104
|
+
"hello",
|
|
105
|
+
"world",
|
|
106
|
+
"test",
|
|
107
|
+
"example",
|
|
108
|
+
"sample",
|
|
109
|
+
"demo",
|
|
110
|
+
"lorem",
|
|
111
|
+
"ipsum"
|
|
112
|
+
]), x = /* @__PURE__ */ new Set(), y = /* @__PURE__ */ new Set();
|
|
113
|
+
let f = !1, p = null, g = null, l = null;
|
|
114
|
+
const z = () => {
|
|
115
|
+
try {
|
|
116
|
+
const t = localStorage.getItem("rte-custom-dictionary");
|
|
117
|
+
t && JSON.parse(t).forEach((e) => x.add(e.toLowerCase()));
|
|
118
|
+
} catch (t) {
|
|
119
|
+
console.warn("Failed to load custom dictionary:", t);
|
|
120
|
+
}
|
|
121
|
+
}, D = () => {
|
|
122
|
+
try {
|
|
123
|
+
const t = Array.from(x);
|
|
124
|
+
localStorage.setItem("rte-custom-dictionary", JSON.stringify(t));
|
|
125
|
+
} catch (t) {
|
|
126
|
+
console.warn("Failed to save custom dictionary:", t);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function F(t, n) {
|
|
130
|
+
const e = [];
|
|
131
|
+
for (let o = 0; o <= n.length; o++) e[o] = [o];
|
|
132
|
+
for (let o = 0; o <= t.length; o++) e[0][o] = o;
|
|
133
|
+
for (let o = 1; o <= n.length; o++)
|
|
134
|
+
for (let s = 1; s <= t.length; s++)
|
|
135
|
+
n.charAt(o - 1) === t.charAt(s - 1) ? e[o][s] = e[o - 1][s - 1] : e[o][s] = Math.min(
|
|
136
|
+
e[o - 1][s - 1] + 1,
|
|
137
|
+
// substitution
|
|
138
|
+
e[o][s - 1] + 1,
|
|
139
|
+
// insertion
|
|
140
|
+
e[o - 1][s] + 1
|
|
141
|
+
// deletion
|
|
142
|
+
);
|
|
143
|
+
return e[n.length][t.length];
|
|
144
|
+
}
|
|
145
|
+
function $(t) {
|
|
146
|
+
const n = t.toLowerCase();
|
|
147
|
+
return N.has(n) || x.has(n) || y.has(n);
|
|
148
|
+
}
|
|
149
|
+
function I(t, n = 5) {
|
|
150
|
+
const e = t.toLowerCase(), s = Array.from(N).map((r) => ({ word: r, distance: F(e, r) }));
|
|
151
|
+
return s.sort((r, i) => r.distance - i.distance), s.filter((r) => r.distance <= 3).slice(0, n).map((r) => r.word);
|
|
152
|
+
}
|
|
153
|
+
function R(t) {
|
|
154
|
+
if (t.nodeType !== Node.ELEMENT_NODE) return !1;
|
|
155
|
+
const n = t;
|
|
156
|
+
return !!(n.closest('code, pre, [contenteditable="false"], .rte-widget, .rte-template, .rte-comment, .rte-merge-tag') || n.hasAttribute("data-comment-id") || n.hasAttribute("data-template") || n.hasAttribute("data-merge-tag"));
|
|
157
|
+
}
|
|
158
|
+
function j(t) {
|
|
159
|
+
const n = [], e = /([\p{L}\p{M}\p{N}\p{Emoji_Presentation}\u200d'-]+|[\uD800-\uDBFF][\uDC00-\uDFFF])/gu;
|
|
160
|
+
let o;
|
|
161
|
+
for (; (o = e.exec(t.data)) !== null; ) {
|
|
162
|
+
const s = o[0], r = o.index, i = r + s.length;
|
|
163
|
+
/https?:\/\//.test(s) || /@/.test(s) || /\{\{.*\}\}/.test(s) || /^\d+$/.test(s) || $(s) || /[a-z][A-Z]/.test(s) || /-/.test(s) || s[0] === s[0].toUpperCase() && s.length > 1 || n.push({
|
|
164
|
+
id: `${s}-${r}`,
|
|
165
|
+
node: t,
|
|
166
|
+
startOffset: r,
|
|
167
|
+
endOffset: i,
|
|
168
|
+
word: s,
|
|
169
|
+
suggestions: I(s),
|
|
170
|
+
ignored: !1
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return n;
|
|
174
|
+
}
|
|
175
|
+
const b = () => {
|
|
176
|
+
const t = window.getSelection();
|
|
177
|
+
if (t && t.rangeCount > 0) {
|
|
178
|
+
let e = t.getRangeAt(0).startContainer;
|
|
179
|
+
for (; e && e !== document.body; ) {
|
|
180
|
+
if (e.nodeType === Node.ELEMENT_NODE) {
|
|
181
|
+
const o = e;
|
|
182
|
+
if (o.getAttribute("contenteditable") === "true")
|
|
183
|
+
return o;
|
|
184
|
+
}
|
|
185
|
+
e = e.parentNode;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const n = document.activeElement;
|
|
189
|
+
if (n) {
|
|
190
|
+
if (n.getAttribute("contenteditable") === "true")
|
|
191
|
+
return n;
|
|
192
|
+
const e = n.closest('[contenteditable="true"]');
|
|
193
|
+
if (e) return e;
|
|
194
|
+
}
|
|
195
|
+
return document.querySelector('[contenteditable="true"]');
|
|
196
|
+
};
|
|
197
|
+
function v() {
|
|
198
|
+
const t = b();
|
|
199
|
+
if (!t) return [];
|
|
200
|
+
const n = [], e = document.createTreeWalker(
|
|
201
|
+
t,
|
|
202
|
+
NodeFilter.SHOW_TEXT,
|
|
203
|
+
{
|
|
204
|
+
acceptNode: (s) => !s.textContent?.trim() || s.parentNode && R(s.parentNode) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
let o = e.nextNode();
|
|
208
|
+
for (; o; )
|
|
209
|
+
n.push(...j(o)), o = e.nextNode();
|
|
210
|
+
return n;
|
|
211
|
+
}
|
|
212
|
+
function w(t) {
|
|
213
|
+
const n = b();
|
|
214
|
+
n && (t || (t = v()), n.querySelectorAll(".rte-misspelled").forEach((e) => {
|
|
215
|
+
const o = e.parentNode;
|
|
216
|
+
if (o) {
|
|
217
|
+
for (; e.firstChild; )
|
|
218
|
+
o.insertBefore(e.firstChild, e);
|
|
219
|
+
o.removeChild(e);
|
|
220
|
+
}
|
|
221
|
+
}), t.forEach((e) => {
|
|
222
|
+
if (y.has(e.word.toLowerCase())) return;
|
|
223
|
+
const o = e.node.data.length;
|
|
224
|
+
if (!(e.startOffset < 0 || e.endOffset > o || e.startOffset >= e.endOffset))
|
|
225
|
+
try {
|
|
226
|
+
const s = document.createRange();
|
|
227
|
+
s.setStart(e.node, e.startOffset), s.setEnd(e.node, e.endOffset);
|
|
228
|
+
const r = document.createElement("span");
|
|
229
|
+
r.className = "rte-misspelled", r.setAttribute("data-word", e.word), r.setAttribute("data-suggestions", e.suggestions.join(",")), r.setAttribute("title", `Suggestions: ${e.suggestions.join(", ")}`), r.style.borderBottom = "2px wavy red", r.style.cursor = "pointer", s.surroundContents(r);
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}), m());
|
|
233
|
+
}
|
|
234
|
+
function C() {
|
|
235
|
+
const t = document.querySelector('[contenteditable="true"]');
|
|
236
|
+
t && t.querySelectorAll(".rte-misspelled").forEach((n) => {
|
|
237
|
+
const e = n.parentNode;
|
|
238
|
+
if (e) {
|
|
239
|
+
for (; n.firstChild; )
|
|
240
|
+
e.insertBefore(n.firstChild, n);
|
|
241
|
+
e.removeChild(n);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function A(t, n) {
|
|
246
|
+
const e = document.createRange();
|
|
247
|
+
e.setStart(t.node, t.startOffset), e.setEnd(t.node, t.endOffset);
|
|
248
|
+
const o = document.createTextNode(n);
|
|
249
|
+
e.deleteContents(), e.insertNode(o);
|
|
250
|
+
}
|
|
251
|
+
function L(t) {
|
|
252
|
+
y.add(t.toLowerCase()), C(), w(), m();
|
|
253
|
+
}
|
|
254
|
+
function T(t) {
|
|
255
|
+
x.add(t.toLowerCase()), D(), C(), w(), m();
|
|
256
|
+
}
|
|
257
|
+
function q(t) {
|
|
258
|
+
const n = b();
|
|
259
|
+
if (!n) return { total: 0, misspelled: 0, accuracy: 100 };
|
|
260
|
+
t || (t = v());
|
|
261
|
+
const e = t.filter((i) => !y.has(i.word.toLowerCase())).length, r = ((n.textContent || "").match(/[\p{L}\p{M}\p{N}]+/gu) || []).length;
|
|
262
|
+
return {
|
|
263
|
+
total: r,
|
|
264
|
+
misspelled: e,
|
|
265
|
+
accuracy: r > 0 ? (r - e) / r * 100 : 100
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function _(t, n, e, o, s) {
|
|
269
|
+
document.querySelectorAll(".rte-spellcheck-menu").forEach((a) => a.remove());
|
|
270
|
+
const r = document.createElement("div");
|
|
271
|
+
if (r.className = "rte-spellcheck-menu", r.style.cssText = `
|
|
272
|
+
position: fixed;
|
|
273
|
+
left: ${t}px;
|
|
274
|
+
top: ${n}px;
|
|
275
|
+
background: #fff;
|
|
276
|
+
border: 1px solid #ccc;
|
|
277
|
+
border-radius: 4px;
|
|
278
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
279
|
+
z-index: 99999;
|
|
280
|
+
padding: 4px 0;
|
|
281
|
+
min-width: 160px;
|
|
282
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
283
|
+
font-size: 13px;
|
|
284
|
+
`, o.slice(0, 5).forEach((a) => {
|
|
285
|
+
const u = document.createElement("div");
|
|
286
|
+
u.className = "rte-spellcheck-menu-item", u.textContent = a, u.style.cssText = "padding: 6px 16px; cursor: pointer; transition: background 0.2s;", u.onmouseenter = () => u.style.backgroundColor = "#f0f0f0", u.onmouseleave = () => u.style.backgroundColor = "transparent", u.onclick = () => {
|
|
287
|
+
const E = s.parentNode;
|
|
288
|
+
if (E) {
|
|
289
|
+
for (const h of E.childNodes)
|
|
290
|
+
if (h.nodeType === Node.TEXT_NODE && h.textContent?.includes(e)) {
|
|
291
|
+
const S = h.textContent.indexOf(e), M = {
|
|
292
|
+
node: h,
|
|
293
|
+
startOffset: S,
|
|
294
|
+
endOffset: S + e.length
|
|
295
|
+
};
|
|
296
|
+
A(M, a);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
r.remove();
|
|
301
|
+
}, r.appendChild(u);
|
|
302
|
+
}), o.length > 0) {
|
|
303
|
+
const a = document.createElement("div");
|
|
304
|
+
a.style.cssText = "height: 1px; background: #ddd; margin: 4px 0;", r.appendChild(a);
|
|
305
|
+
}
|
|
306
|
+
const i = document.createElement("div");
|
|
307
|
+
i.className = "rte-spellcheck-menu-item", i.textContent = "Ignore Once", i.style.cssText = "padding: 6px 16px; cursor: pointer; color: #666;", i.onmouseenter = () => i.style.backgroundColor = "#f0f0f0", i.onmouseleave = () => i.style.backgroundColor = "transparent", i.onclick = () => {
|
|
308
|
+
s.remove(), r.remove();
|
|
309
|
+
}, r.appendChild(i);
|
|
310
|
+
const d = document.createElement("div");
|
|
311
|
+
d.className = "rte-spellcheck-menu-item", d.textContent = "Ignore All", d.style.cssText = "padding: 6px 16px; cursor: pointer; color: #666;", d.onmouseenter = () => d.style.backgroundColor = "#f0f0f0", d.onmouseleave = () => d.style.backgroundColor = "transparent", d.onclick = () => {
|
|
312
|
+
L(e), r.remove();
|
|
313
|
+
}, r.appendChild(d);
|
|
314
|
+
const c = document.createElement("div");
|
|
315
|
+
c.className = "rte-spellcheck-menu-item", c.textContent = "Add to Dictionary", c.style.cssText = "padding: 6px 16px; cursor: pointer; color: #1976d2;", c.onmouseenter = () => c.style.backgroundColor = "#f0f0f0", c.onmouseleave = () => c.style.backgroundColor = "transparent", c.onclick = () => {
|
|
316
|
+
T(e), r.remove();
|
|
317
|
+
}, r.appendChild(c), document.body.appendChild(r);
|
|
318
|
+
const k = (a) => {
|
|
319
|
+
r.contains(a.target) || (r.remove(), document.removeEventListener("mousedown", k));
|
|
320
|
+
};
|
|
321
|
+
setTimeout(() => document.addEventListener("mousedown", k), 0);
|
|
322
|
+
}
|
|
323
|
+
function B() {
|
|
324
|
+
document.addEventListener("contextmenu", (t) => {
|
|
325
|
+
const n = t.target;
|
|
326
|
+
if (n && n.classList.contains("rte-misspelled")) {
|
|
327
|
+
t.preventDefault();
|
|
328
|
+
const e = n.getAttribute("data-word"), o = (n.getAttribute("data-suggestions") || "").split(",").filter((s) => s);
|
|
329
|
+
_(t.clientX, t.clientY, e, o, n);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function H() {
|
|
334
|
+
const t = document.createElement("div");
|
|
335
|
+
return t.className = "rte-spell-check-panel", t.style.cssText = `
|
|
336
|
+
position: fixed;
|
|
337
|
+
right: 0;
|
|
338
|
+
top: 0;
|
|
339
|
+
width: 350px;
|
|
340
|
+
height: 100vh;
|
|
341
|
+
background: white;
|
|
342
|
+
border-left: 1px solid #ddd;
|
|
343
|
+
box-shadow: -2px 0 4px rgba(0,0,0,0.1);
|
|
344
|
+
overflow-y: auto;
|
|
345
|
+
z-index: 10000;
|
|
346
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
347
|
+
padding: 16px;
|
|
348
|
+
`, document.body.appendChild(t), t;
|
|
349
|
+
}
|
|
350
|
+
function m() {
|
|
351
|
+
if (!l) return;
|
|
352
|
+
const t = v(), n = q(t);
|
|
353
|
+
l.innerHTML = `
|
|
354
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
355
|
+
<h3 style="margin: 0; font-size: 16px; font-weight: 600;">Spell Check</h3>
|
|
356
|
+
<button class="rte-spellcheck-close" style="background: none; border: none; font-size: 22px; cursor: pointer; color: #888;">✕</button>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<div style="display: flex; gap: 24px; margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid #eee;">
|
|
360
|
+
<div>
|
|
361
|
+
<div style="font-size: 12px; color: #999;">Total Words</div>
|
|
362
|
+
<div style="font-size: 16px; font-weight: 600; color: #333;">${n.total}</div>
|
|
363
|
+
</div>
|
|
364
|
+
<div>
|
|
365
|
+
<div style="font-size: 12px; color: #999;">Misspelled</div>
|
|
366
|
+
<div style="font-size: 16px; font-weight: 600; color: #d32f2f;">${n.misspelled}</div>
|
|
367
|
+
</div>
|
|
368
|
+
<div>
|
|
369
|
+
<div style="font-size: 12px; color: #999;">Accuracy</div>
|
|
370
|
+
<div style="font-size: 16px; font-weight: 600; color: #388e3c;">${n.accuracy.toFixed(1)}%</div>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<div class="misspellings-list">
|
|
375
|
+
${t.length === 0 ? '<div style="padding: 12px; text-align: center; color: #999; font-size: 13px;">No spelling errors found</div>' : t.map((o, s) => `
|
|
376
|
+
<div class="misspelling-item" data-word="${o.word}" data-index="${s}" style="padding: 8px; margin-bottom: 8px; background-color: #f5f5f5; border-radius: 4px;">
|
|
377
|
+
<div class="word-header" style="display: flex; justify-content: space-between; align-items: center; cursor: pointer;">
|
|
378
|
+
<span style="font-weight: 600; color: #d32f2f;">${o.word}</span>
|
|
379
|
+
<button class="expand-btn" style="background: none; border: none; cursor: pointer; font-size: 12px; color: #666;">▶</button>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="suggestions" style="display: none; margin-top: 8px; padding-top: 8px; border-top: 1px solid #ddd;">
|
|
382
|
+
${o.suggestions.length > 0 ? `<div style="font-size: 12px; font-weight: 600; color: #333; margin-bottom: 6px;">Suggestions:</div>
|
|
383
|
+
${o.suggestions.map((r) => `<button class="suggestion-btn" data-suggestion="${r}" style="display: inline-block; margin-right: 6px; margin-bottom: 6px; padding: 4px 8px; background-color: #1976d2; color: white; border: none; border-radius: 3px; font-size: 12px; cursor: pointer;">${r}</button>`).join("")}` : '<div style="font-size: 12px; color: #999; margin-bottom: 8px;">No suggestions</div>'}
|
|
384
|
+
<div style="margin-top: 8px; display: flex; gap: 6px;">
|
|
385
|
+
<button class="ignore-btn" style="font-size: 12px; padding: 4px 8px; background-color: white; border: 1px solid #ddd; border-radius: 3px; cursor: pointer;">Ignore</button>
|
|
386
|
+
<button class="add-btn" style="font-size: 12px; padding: 4px 8px; background-color: white; border: 1px solid #ddd; border-radius: 3px; cursor: pointer;">Add to Dictionary</button>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
`).join("")}
|
|
391
|
+
</div>
|
|
392
|
+
`, l.querySelector(".rte-spellcheck-close")?.addEventListener("click", () => {
|
|
393
|
+
O();
|
|
394
|
+
}), l.querySelectorAll(".word-header").forEach((o, s) => {
|
|
395
|
+
o.addEventListener("click", () => {
|
|
396
|
+
const i = o.closest(".misspelling-item")?.querySelector(".suggestions"), d = o.querySelector(".expand-btn");
|
|
397
|
+
i && d && (i.style.display === "none" ? (i.style.display = "block", d.textContent = "▼") : (i.style.display = "none", d.textContent = "▶"));
|
|
398
|
+
});
|
|
399
|
+
}), l.querySelectorAll(".suggestion-btn").forEach((o) => {
|
|
400
|
+
o.addEventListener("click", () => {
|
|
401
|
+
const s = o.getAttribute("data-suggestion"), r = o.closest(".misspelling-item");
|
|
402
|
+
r?.getAttribute("data-word");
|
|
403
|
+
const i = parseInt(r?.getAttribute("data-index") || "0");
|
|
404
|
+
t[i] && A(t[i], s);
|
|
405
|
+
});
|
|
406
|
+
}), l.querySelectorAll(".ignore-btn").forEach((o) => {
|
|
407
|
+
o.addEventListener("click", () => {
|
|
408
|
+
const r = o.closest(".misspelling-item")?.getAttribute("data-word");
|
|
409
|
+
L(r);
|
|
410
|
+
});
|
|
411
|
+
}), l.querySelectorAll(".add-btn").forEach((o) => {
|
|
412
|
+
o.addEventListener("click", () => {
|
|
413
|
+
const r = o.closest(".misspelling-item")?.getAttribute("data-word");
|
|
414
|
+
T(r);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function W() {
|
|
419
|
+
const t = b();
|
|
420
|
+
t && (p && p.disconnect(), p = new MutationObserver((n) => {
|
|
421
|
+
n.some((e) => e.type === "characterData" || e.type === "childList") && (g && clearTimeout(g), g = window.setTimeout(() => {
|
|
422
|
+
f && (w(), m());
|
|
423
|
+
}, 350));
|
|
424
|
+
}), p.observe(t, {
|
|
425
|
+
characterData: !0,
|
|
426
|
+
childList: !0,
|
|
427
|
+
subtree: !0
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
function P() {
|
|
431
|
+
p && (p.disconnect(), p = null), g && (clearTimeout(g), g = null);
|
|
432
|
+
}
|
|
433
|
+
function O() {
|
|
434
|
+
return f = !f, f ? (w(), B(), W(), l || (l = H(), m())) : (C(), P(), l && (l.remove(), l = null)), f;
|
|
435
|
+
}
|
|
436
|
+
const V = () => ({
|
|
437
|
+
name: "spellCheck",
|
|
438
|
+
init: () => {
|
|
439
|
+
z();
|
|
440
|
+
},
|
|
441
|
+
toolbar: [
|
|
442
|
+
{
|
|
443
|
+
label: "Spell Check",
|
|
444
|
+
command: "toggleSpellCheck",
|
|
445
|
+
icon: '<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 12.5L3.84375 9.5M3.84375 9.5L5 5.38889C5 5.38889 5.25 4.5 6 4.5C6.75 4.5 7 5.38889 7 5.38889L8.15625 9.5M3.84375 9.5H8.15625M9 12.5L8.15625 9.5M13 16.8333L15.4615 19.5L21 13.5M12 8.5H15C16.1046 8.5 17 7.60457 17 6.5C17 5.39543 16.1046 4.5 15 4.5H12V8.5ZM12 8.5H16C17.1046 8.5 18 9.39543 18 10.5C18 11.6046 17.1046 12.5 16 12.5H12V8.5Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
|
|
446
|
+
shortcut: "F7"
|
|
447
|
+
}
|
|
448
|
+
],
|
|
449
|
+
commands: {
|
|
450
|
+
toggleSpellCheck: () => (O(), !0)
|
|
451
|
+
},
|
|
452
|
+
keymap: {
|
|
453
|
+
F7: "toggleSpellCheck"
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
export {
|
|
457
|
+
V as SpellCheckPlugin
|
|
458
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@editora/spell-check",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Spell Check plugin for Rich Text Editor - Non-blocking spell checking with suggestions",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "vite build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"editor",
|
|
17
|
+
"spell-check",
|
|
18
|
+
"spell-checker",
|
|
19
|
+
"language"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/ajaykr089/Editora.git"
|
|
24
|
+
},
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@editora/core": "^1.0.5",
|
|
29
|
+
"@editora/toast": "^2.0.2"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"react-dom": "^18.2.0"
|
|
34
|
+
}
|
|
35
|
+
}
|