@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.
@@ -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
+ }