`
16 | * Pure Javascript - requires no external libraries.
17 | * Fast and lightweight - less than 4KB, minified and gzipped.
18 | * Tested in IE7+, Firefox 4+, Safari 4+, and Chrome.
19 |
20 | ### Requirements ###
21 |
22 | + PHP 5.3+ with Pspell extension installed
23 |
24 | ### Getting Started ###
25 | Copy `spellcheck.php` to your web directory. Include `spellcheck.min.css` and `spellcheck.min.js` into your page:
26 |
27 | ```html
28 |
29 |
30 |
31 |
32 | ```
33 |
34 | Initialize the spell checker when the DOM is ready. There are three required parameters:
35 |
36 | ```javascript
37 | var checker = new sc.SpellChecker({
38 | button: 'spellcheck_button', // HTML element that will open the spell checker when clicked
39 | textInput: 'text_box', // HTML field containing the text to spell check
40 | action: 'spellcheck.php' // URL of the server side script
41 | });
42 | ```
43 |
44 | ```html
45 |
46 |
47 | ```
48 |
49 | ### Installing Pspell ###
50 | You'll need to install aspell, a dictionary, and the php-pspell module if not already installed:
51 |
52 | ```
53 | sudo yum install aspell aspell-en
54 | sudo yum install php-pspell
55 | ```
56 |
57 | Then restart Apache:
58 |
59 | ```
60 | sudo service httpd restart
61 | ```
62 |
63 | ### API Reference ###
64 |
65 | #### Settings ####
66 |
67 |
68 |
69 |
70 | Name
71 | Type
72 | Description
73 |
74 |
75 |
76 |
77 | action
78 | String Default: ""
79 | Location of spellcheck.php on the server.
80 |
81 |
82 |
83 | button
84 | Mixed Default: ""
85 | Button that opens spell checker. Accepts an element ID string, element, or jQuery object.
86 |
87 |
88 |
89 | textInput
90 | Mixed Default: ""
91 | Text input to spell check. Accepts an element ID string, element, or jQuery object.
92 |
93 |
94 |
95 | name
96 | String Default: ""
97 | Parameter name of text sent to server.
98 |
99 |
100 |
101 | data
102 | Object Default: {}
103 | Additional data to send to the server.
104 |
105 |
106 |
107 | debug
108 | Boolean Default: false
109 | Set to true
to log progress messages and server response in the console.
110 |
111 |
112 |
113 |
114 |
115 | #### Callback Functions ####
116 |
117 |
118 |
119 |
120 | Name
121 | Arguments
122 | Description
123 |
124 |
125 |
126 |
127 | onOpen
128 | button
(Element),text
(String)
129 | Function to be called when spell checker is opened, after successful server response. The function gets passed two arguments: (1) a reference to the spell check button; (2) a string containing the text that is to be spell checked.
130 |
131 |
132 |
133 | onClose
134 | button
(Element),text
(String)
135 | Function to be called after the spell checker is closed. The function gets passed two arguments: (1) a reference to the spell check button; (2) a string containing the spell checked text, including any changes.
136 |
137 |
138 |
139 |
140 | #### Instance Methods ####
141 |
142 |
143 |
144 |
145 | Name
146 | Arguments
147 | Description
148 |
149 |
150 |
151 |
152 | destroy
153 | none
154 | Completely removes spell check functionality. All event listeners, CSS classes, and DOM elements added by the plugin are removed.
155 |
156 |
157 |
158 |
159 | ### License ###
160 | Released under the MIT license.
161 |
162 | [](http://githalytics.com/LPology/Javascript-PHP-Spell-Checker)
--------------------------------------------------------------------------------
/css/spellcheck.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript/PHP Spell Checker
3 | * Version 1.6
4 | * https://github.com/LPology/Javascript-PHP-Spell-Checker
5 | *
6 | * Copyright 2012-2015 LPology, LLC
7 | * Released under the MIT license
8 | */
9 |
10 | div.spell-wrap,
11 | div.spell-msg,
12 | div.spell-wrap div,
13 | div.spell-msg div,
14 | div.spell-wrap input,
15 | div.spell-wrap input[type="text"],
16 | div.spell-wrap hr {
17 | -webkit-box-sizing: content-box;
18 | -moz-box-sizing: content-box;
19 | box-sizing: content-box;
20 | }
21 |
22 | div.spell-wrap div {
23 | display: block;
24 | }
25 |
26 | .spellcheck-trigger:hover {
27 | cursor: pointer;
28 | }
29 |
30 | div.spell-wrap button,
31 | div.spell-wrap input[type="button"],
32 | div.spell-wrap input,
33 | div.spell-wrap input[type="text"],
34 | div.spell-wrap select {
35 | margin: 0;
36 | vertical-align: middle;
37 | display: inline-block;
38 | line-height: inherit;
39 | font-size: inherit;
40 | font-family: inherit;
41 | font-weight: inherit;
42 | *display: inline;
43 | *zoom: 1;
44 | }
45 |
46 | div.spell-wrap button::-moz-focus-inner,
47 | div.spell-wrap input::-moz-focus-inner {
48 | padding: 0;
49 | border: 0;
50 | }
51 |
52 | div.spell-wrap input,
53 | div.spell-wrap input[type="text"] {
54 | height: 18px;
55 | line-height: 18px;
56 | padding: 3px;
57 | color: #404040;
58 | border: 1px solid #bbbbbb;
59 | -webkit-border-radius: 1px;
60 | -moz-border-radius: 1px;
61 | border-radius: 1px;
62 | }
63 |
64 | div.spell-wrap button,
65 | div.spell-wrap input[type="button"] {
66 | line-height: 20px;
67 | cursor: pointer;
68 | width: 130px;
69 | -webkit-appearance: button;
70 | -moz-appearance: button;
71 | }
72 |
73 | div.spell-wrap div.clearleft {
74 | clear: both;
75 | float: left;
76 | width: 100%;
77 | margin-top: 8px;
78 | }
79 |
80 | div.spell-wrap span.word-highlight {
81 | color: #ff0000;
82 | }
83 |
84 | div.spell-wrap hr {
85 | clear: both;
86 | border: none;
87 | height: 1px;
88 | background: #cccccc;
89 | color: #cccccc;
90 | margin: 15px 0;
91 | float: left;
92 | width: 100%;
93 | }
94 |
95 | div.spell-check-overlay {
96 | display: none;
97 | position: fixed;
98 | top: 0;
99 | left: 0;
100 | width: 200%;
101 | height: 200%;
102 | background-color: #111;
103 | opacity: 0.22;
104 | filter: alpha(opacity=22);
105 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=22)";
106 | z-index: 5000;
107 | }
108 |
109 | div.spell-header {
110 | clear: both;
111 | position: relative;
112 | padding: 3px;
113 | }
114 |
115 | div.spell-header > div {
116 | vertical-align:middle;
117 | padding: 8px 12px 6px;
118 | font-weight: bold;
119 | background-color: #F5F5F5;
120 | -webkit-box-shadow: inset 0 1px 0 #ffffff;
121 | -moz-box-shadow: inset 0 1px 0 #ffffff;
122 | box-shadow: inset 0 1px 0 #ffffff;
123 | -webkit-border-radius: 4px; /* Safari 4 */
124 | -moz-border-radius: 4px; /* Firefox 3.6 */
125 | border-radius: 4px;
126 | border: 0 0 0 1px solid;
127 | border-color: white;
128 | text-decoration: none;
129 | -webkit-border-bottom-right-radius: 0; /* Safari 4 */
130 | -moz-border-radius-bottomright: 0; /* Firefox 3.6 */
131 | border-bottom-right-radius: 0;
132 | -webkit-border-bottom-left-radius: 0; /* Safari 4 */
133 | -moz-border-radius-bottomleft: 0; /* Firefox 3.6 */
134 | border-bottom-left-radius: 0;
135 | border-bottom: 1px solid #ddd;
136 | }
137 |
138 | div.spell-msg div.spell-header {
139 | margin-bottom: 8px;
140 | text-align:left;
141 | }
142 |
143 | div.spell-wrap,
144 | div.spell-msg {
145 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
146 | background-color: #ffffff;
147 | display: none;
148 | position: fixed;
149 | color: #454545;
150 | font-size: 15px;
151 | font-weight: normal;
152 | font-weight: 200;
153 | top: 50%;
154 | left: 50%;
155 | height: auto;
156 | line-height: 23px;
157 | -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); /* Safari 4 */
158 | -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); /* Firefox 3.6 */
159 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
160 | }
161 |
162 | div.spell-wrap {
163 | padding: 0;
164 | width: 520px;
165 | margin-left: -260px;
166 | margin-top: -218px;
167 | z-index: 4999;
168 | }
169 |
170 | div.spell-wrap,
171 | div.spell-msg {
172 | border: 1px solid #cccccc;
173 | border: 1px solid rgba(0, 0, 0, 0.4);
174 | border-radius: 6px 6px 6px 6px;
175 | }
176 |
177 | div.spelling-inner {
178 | padding: 2px 15px 8px;
179 | }
180 |
181 | div.spell-wrap div.spell-header {
182 | font-size: 17px;
183 | line-height: 17px;
184 | }
185 |
186 | div.spell-wrap div.spell-header div {
187 | padding: 12px 15px 11px;
188 | }
189 |
190 | div.spell-wrap input.current {
191 | width: 100%;
192 | margin-bottom: 5px;
193 | }
194 |
195 | div.spell-wrap div.context {
196 | clear: both;
197 | float: left;
198 | color: #333;
199 | width: 100%;
200 | height: 70px;
201 | line-height: 20px;
202 | background-color: #fff;
203 | padding: 1px 3px;
204 | -webkit-border-radius: 1px;
205 | -moz-border-radius: 1px;
206 | border-radius: 1px;
207 | border: 1px solid;
208 | border-color: #bbbbbb #bbbbbb #bbbbbb #cccccc;
209 | -moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);
210 | -webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);
211 | box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);
212 | overflow: auto;
213 | }
214 |
215 | div.spell-msg {
216 | width: 300px;
217 | min-height: 70px;
218 | margin-left: -150px;
219 | margin-top: -55px;
220 | padding-bottom: 8px;
221 | text-align: center;
222 | z-index: 5002;
223 | }
224 |
225 | div.close-box button {
226 | cursor: pointer;
227 | display: inline-block;
228 | padding: 4px 8px;
229 | width: auto;
230 | margin-bottom: 0;
231 | font-size: 14px;
232 | font-weight: normal;
233 | line-height: 1.428571429;
234 | text-align: center;
235 | white-space: nowrap;
236 | vertical-align: middle;
237 | cursor: pointer;
238 | border: 1px solid #ccc;
239 | -webkit-user-select: none;
240 | -moz-user-select: none;
241 | -ms-user-select: none;
242 | -o-user-select: none;
243 | user-select: none;
244 | color: #ffffff;
245 | background-color:#3276b1;border-color:#2c699d;
246 | -webkit-border-radius: 3px;
247 | -moz-border-radius: 3px;
248 | border-radius: 3px;
249 | }
250 | div.close-box button:hover,
251 | div.close-box button:focus,
252 | div.close-box button:active {
253 | color: #ffffff;
254 | background-color:#296191;
255 | border-color:#1f496d;
256 | text-decoration: none;
257 | }
258 |
259 | div.close-box button:focus {
260 | outline: thin dotted #333;
261 | outline: 5px auto -webkit-focus-ring-color;
262 | outline-offset: -2px;
263 | }
264 |
265 | div.spell-msg div.spell-msg-inner {
266 | margin: 0 auto;
267 | height: auto;
268 | clear: both;
269 | padding: 6px 0;
270 | text-align: center;
271 | }
272 |
273 | div.spell-msg div.spell-msg-inner button {
274 | margin: 0;
275 | width: 70px;
276 | margin-top: 2px;
277 | margin-bottom: 2px;
278 | }
279 |
280 | div.spell-wrap div.spell-suggest,
281 | div.spell-wrap div.spell-nf {
282 | clear: both;
283 | float: left;
284 | width: 328px;
285 | height: auto;
286 | }
287 |
288 | div.spell-wrap div.spell-nf {
289 | width: 320px;
290 | }
291 |
292 | div.spell-wrap div.spell-ignorebtns,
293 | div.spell-wrap div.spell-changebtns {
294 | float: right;
295 | height: auto;
296 | }
297 |
298 | div.spell-wrap div.spell-changebtns button,
299 | div.spell-wrap div.spell-ignorebtns button {
300 | display: block;
301 | margin-bottom: 8px;
302 | clear: both;
303 | }
304 |
305 | div.spell-wrap div.spell-changebtns button:last-child,
306 | div.spell-wrap div.spell-ignorebtns button:last-child {
307 | margin-bottom: 0;
308 | }
309 |
310 | div.spell-wrap div.spell-suggest select {
311 | background-color: #ffffff;
312 | border: 1px solid #bbbbbb;
313 | -webkit-border-radius: 3px;
314 | -moz-border-radius: 3px;
315 | border-radius: 3px;
316 | font-family: inherit;
317 | display: inline-block;
318 | font-size: inherit;
319 | line-height: inherit;
320 | font-weight: inherit;
321 | padding: 0;
322 | margin: 0;
323 | width: 100%;
324 | height: auto !important;
325 | *display: inline;
326 | *zoom: 1;
327 | }
328 |
329 | div.spell-wrap div.spell-suggest select option {
330 | font-size: inherit;
331 | line-height: inherit;
332 | }
333 |
334 | div.spell-wrap div.close-box {
335 | clear: both;
336 | width: 100%;
337 | text-align: right;
338 | padding-bottom: 6px;
339 | }
340 |
341 | div.spell-wrap,
342 | div.spell-wrap div,
343 | div.spell-msg,
344 | div.spell-msg div,
345 | div.spell-check-overlay {
346 | *zoom: 1;
347 | }
--------------------------------------------------------------------------------
/css/spellcheck.min.css:
--------------------------------------------------------------------------------
1 | /** Javascript/PHP Spell Checker v1.6 https://github.com/LPology/Javascript-PHP-Spell-Checker Copyright 2012-2015 LPology, LLC Released under the MIT license */
2 | div.spell-wrap,div.spell-msg,div.spell-wrap div,div.spell-msg div,div.spell-wrap input,div.spell-wrap input[type="text"],div.spell-wrap hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}div.spell-wrap div{display:block}.spellcheck-trigger:hover{cursor:pointer}div.spell-wrap button,div.spell-wrap input[type="button"],div.spell-wrap input,div.spell-wrap input[type="text"],div.spell-wrap select{margin:0;vertical-align:middle;display:inline-block;line-height:inherit;font-size:inherit;font-family:inherit;font-weight:inherit;*display:inline;*zoom:1}div.spell-wrap button::-moz-focus-inner,div.spell-wrap input::-moz-focus-inner{padding:0;border:0}div.spell-wrap input,div.spell-wrap input[type="text"]{height:18px;line-height:18px;padding:3px;color:#404040;border:1px solid #bbb;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px}div.spell-wrap button,div.spell-wrap input[type="button"]{line-height:20px;cursor:pointer;width:130px;-webkit-appearance:button;-moz-appearance:button}div.spell-wrap div.clearleft{clear:both;float:left;width:100%;margin-top:8px}div.spell-wrap span.word-highlight{color:red}div.spell-wrap hr{clear:both;border:0;height:1px;background:#ccc;color:#ccc;margin:15px 0;float:left;width:100%}div.spell-check-overlay{display:none;position:fixed;top:0;left:0;width:200%;height:200%;background-color:#111;opacity:.22;filter:alpha(opacity=22);-ms-filter:"alpha(opacity=22)";z-index:5000}div.spell-header{clear:both;position:relative;padding:3px}div.spell-header>div{vertical-align:middle;padding:8px 12px 6px;font-weight:bold;background-color:#f5f5f5;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0 0 0 1px solid;border-color:white;text-decoration:none;-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;border-bottom:1px solid #ddd}div.spell-msg div.spell-header{margin-bottom:8px;text-align:left}div.spell-wrap,div.spell-msg{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;background-color:#fff;display:none;position:fixed;color:#454545;font-size:15px;font-weight:normal;font-weight:200;top:50%;left:50%;height:auto;line-height:23px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3)}div.spell-wrap{padding:0;width:520px;margin-left:-260px;margin-top:-218px;z-index:4999}div.spell-wrap,div.spell-msg{border:1px solid #ccc;border:1px solid rgba(0,0,0,0.4);border-radius:6px 6px 6px 6px}div.spelling-inner{padding:2px 15px 8px}div.spell-wrap div.spell-header{font-size:17px;line-height:17px}div.spell-wrap div.spell-header div{padding:12px 15px 11px}div.spell-wrap input.current{width:100%;margin-bottom:5px}div.spell-wrap div.context{clear:both;float:left;color:#333;width:100%;height:70px;line-height:20px;background-color:#fff;padding:1px 3px;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;border:1px solid;border-color:#bbb #bbb #bbb #ccc;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);overflow:auto}div.spell-msg{width:300px;min-height:70px;margin-left:-150px;margin-top:-55px;padding-bottom:8px;text-align:center;z-index:5002}div.close-box button{cursor:pointer;display:inline-block;padding:4px 8px;width:auto;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid #ccc;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;color:#fff;background-color:#3276b1;border-color:#2c699d;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}div.close-box button:hover,div.close-box button:focus,div.close-box button:active{color:#fff;background-color:#296191;border-color:#1f496d;text-decoration:none}div.close-box button:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}div.spell-msg div.spell-msg-inner{margin:0 auto;height:auto;clear:both;padding:6px 0;text-align:center}div.spell-msg div.spell-msg-inner button{margin:0;width:70px;margin-top:2px;margin-bottom:2px}div.spell-wrap div.spell-suggest,div.spell-wrap div.spell-nf{clear:both;float:left;width:328px;height:auto}div.spell-wrap div.spell-nf{width:320px}div.spell-wrap div.spell-ignorebtns,div.spell-wrap div.spell-changebtns{float:right;height:auto}div.spell-wrap div.spell-changebtns button,div.spell-wrap div.spell-ignorebtns button{display:block;margin-bottom:8px;clear:both}div.spell-wrap div.spell-changebtns button:last-child,div.spell-wrap div.spell-ignorebtns button:last-child{margin-bottom:0}div.spell-wrap div.spell-suggest select{background-color:#fff;border:1px solid #bbb;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;font-family:inherit;display:inline-block;font-size:inherit;line-height:inherit;font-weight:inherit;padding:0;margin:0;width:100%;height:auto !important;*display:inline;*zoom:1}div.spell-wrap div.spell-suggest select option{font-size:inherit;line-height:inherit}div.spell-wrap div.close-box{clear:both;width:100%;text-align:right;padding-bottom:6px}div.spell-wrap,div.spell-wrap div,div.spell-msg,div.spell-msg div,div.spell-check-overlay{*zoom:1}
3 |
--------------------------------------------------------------------------------
/js/spellcheck.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Javascript/PHP Spell Checker
3 | * Version 1.6.1
4 | * https://github.com/LPology/Javascript-PHP-Spell-Checker
5 | *
6 | * Copyright 2012-2015 LPology, LLC
7 | * Released under the MIT license
8 | */
9 |
10 | ;(function( window, document, undefined ) {
11 |
12 | var sc = window.sc || {},
13 |
14 | // Pre-compile and cache our regular expressions
15 | rLWhitespace = /^\s+/,
16 | rTWhitespace = /\s+$/,
17 | rLNonWhitespace = /[^\s]+/,
18 | rRNonWhitespace = /[^\s]+$/,
19 |
20 | // sc.getUID
21 | uidReplace = /[xy]/g,
22 |
23 | //sc.encodeHTML()
24 | rAmp = /&/g,
25 | rQuot = /"/g,
26 | rQuot2 = /'/g,
27 | rLt = //g,
29 |
30 | rAlphaNum = /^\w+$/,
31 |
32 | // Holds cached regular expressions for _getRegex()
33 | regexCache = {},
34 |
35 | _ = function( elem ) {
36 | return document.getElementById( elem );
37 | };
38 |
39 | /**
40 | * Accepts an object and returns an array of its property names
41 | */
42 | sc.objectKeys = function( obj ) {
43 | "use strict";
44 |
45 | var keys = [];
46 | for ( var prop in obj ) {
47 | if ( obj.hasOwnProperty( prop ) ) {
48 | keys.push( prop );
49 | }
50 | }
51 | return keys;
52 | };
53 |
54 | /**
55 | * Converts object to query string
56 | */
57 | sc.obj2string = function( obj, prefix ) {
58 | "use strict";
59 |
60 | var str = [];
61 |
62 | for ( var prop in obj ) {
63 | if ( obj.hasOwnProperty( prop ) ) {
64 | var k = prefix ? prefix + '[' + prop + ']' : prop, v = obj[prop];
65 | str.push( typeof v === 'object' ?
66 | sc.obj2string( v, k ) :
67 | encodeURIComponent( k ) + '=' + encodeURIComponent( v ) );
68 | }
69 | }
70 |
71 | return str.join( '&' );
72 | };
73 |
74 | /**
75 | * Copies all missing properties from second object to first object
76 | */
77 | sc.extendObj = function( first, second ) {
78 | "use strict";
79 |
80 | for ( var prop in second ) {
81 | if ( second.hasOwnProperty( prop ) ) {
82 | first[prop] = second[prop];
83 | }
84 | }
85 | };
86 |
87 | /**
88 | * Returns true if an object has no properties of its own
89 | */
90 | sc.isEmpty = function( obj ) {
91 | "use strict";
92 |
93 | for ( var prop in obj ) {
94 | if ( obj.hasOwnProperty( prop ) ) {
95 | return false;
96 | }
97 | }
98 | return true;
99 | };
100 |
101 | sc.contains = function( array, item ) {
102 | "use strict";
103 |
104 | var i = array.length;
105 | while ( i-- ) {
106 | if ( array[i] === item ) {
107 | return true;
108 | }
109 | }
110 | return false;
111 | };
112 |
113 | /**
114 | * Nulls out event handlers to prevent memory leaks in IE6/IE7
115 | * http://javascript.crockford.com/memory/leak.html
116 | * @param {Element} d
117 | * @return void
118 | */
119 | sc.purge = function( d ) {
120 | "use strict";
121 |
122 | var a = d.attributes, i, l, n;
123 |
124 | if ( a ) {
125 | for ( i = a.length - 1; i >= 0; i -= 1 ) {
126 | n = a[i].name;
127 |
128 | if ( typeof d[n] === 'function' ) {
129 | d[n] = null;
130 | }
131 | }
132 | }
133 |
134 | a = d.childNodes;
135 |
136 | if ( a ) {
137 | l = a.length;
138 | for ( i = 0; i < l; i += 1 ) {
139 | sc.purge( d.childNodes[i] );
140 | }
141 | }
142 | };
143 |
144 | /**
145 | * Removes element from the DOM
146 | */
147 | sc.remove = function( elem ) {
148 | "use strict";
149 |
150 | if ( elem && elem.parentNode ) {
151 | // null out event handlers for IE
152 | sc.purge( elem );
153 | elem.parentNode.removeChild( elem );
154 | }
155 | elem = null;
156 | };
157 |
158 | /**
159 | * Removes whtie space from left and right of string
160 | */
161 | var trim = "".trim;
162 |
163 | sc.trim = trim && !trim.call("\uFEFF\xA0") ?
164 | function( text ) {
165 | return text === null ?
166 | "" :
167 | trim.call( text );
168 | } :
169 | function( text ) {
170 | return text === null ?
171 | "" :
172 | text.toString().replace( rLWhitespace, '' ).replace( rTWhitespace, '' );
173 | };
174 |
175 | /**
176 | * Generates unique ID
177 | * Complies with RFC 4122 version 4
178 | * http://stackoverflow.com/a/2117523/1091949
179 | */
180 | sc.getId = function() {
181 | "use strict";
182 |
183 | /*jslint bitwise: true*/
184 | return 'axxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(uidReplace, function(c) {
185 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
186 | return v.toString(16);
187 | });
188 | };
189 |
190 | sc.addEvent = function( elem, type, fn ) {
191 | "use strict";
192 |
193 | if ( typeof elem === 'string' ) {
194 | elem = document.getElementById( elem );
195 | }
196 |
197 | if ( elem.addEventListener ) {
198 | elem.addEventListener( type, fn, false );
199 |
200 | } else {
201 | elem.attachEvent( 'on' + type, fn );
202 | }
203 |
204 | return function() {
205 | sc.removeEvent( elem, type, fn );
206 | };
207 | };
208 |
209 | sc.removeEvent = function(elem, type, fn) {
210 | "use strict";
211 |
212 | if (typeof elem === 'string') {
213 | elem = document.getElementById( elem );
214 | }
215 |
216 | if ( elem.removeEventListener ) {
217 | elem.removeEventListener( type, fn, false );
218 |
219 | } else {
220 | elem.detachEvent( 'on' + type, fn );
221 | }
222 | };
223 |
224 | sc.newXHR = function() {
225 | "use strict";
226 |
227 | if ( typeof XMLHttpRequest !== 'undefined' ) {
228 | return new window.XMLHttpRequest();
229 |
230 | } else if ( window.ActiveXObject ) {
231 | try {
232 | return new window.ActiveXObject( 'Microsoft.XMLHTTP' );
233 | } catch ( err ) {
234 | return false;
235 | }
236 | }
237 | };
238 |
239 | sc.encodeHTML = function( str ) {
240 | "use strict";
241 |
242 | return String( str )
243 | .replace( rAmp, '&' )
244 | .replace( rQuot, '"' )
245 | .replace( rQuot2, ''' )
246 | .replace( rLt, '<' )
247 | .replace( rGt, '>' );
248 | };
249 |
250 | /**
251 | * Parses a JSON string and returns a Javascript object
252 | * Parts borrowed from www.jquery.com
253 | */
254 | sc.parseJSON = function( data ) {
255 | "use strict";
256 |
257 | if ( !data ) {
258 | return false;
259 | }
260 |
261 | data = sc.trim( data + '' );
262 |
263 | if ( window.JSON && window.JSON.parse ) {
264 | try {
265 | // Support: Android 2.3
266 | // Workaround failure to string-cast null input
267 | return window.JSON.parse( data + '' );
268 | } catch ( err ) {
269 | return false;
270 | }
271 | }
272 |
273 | var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g,
274 | depth = null,
275 | requireNonComma;
276 |
277 | // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
278 | // after removing valid tokens
279 | return data && !sc.trim( data.replace( rvalidtokens, function( token, comma, open, close ) {
280 |
281 | // Force termination if we see a misplaced comma
282 | if ( requireNonComma && comma ) {
283 | depth = 0;
284 | }
285 |
286 | // Perform no more replacements after returning to outermost depth
287 | if ( depth === 0 ) {
288 | return token;
289 | }
290 |
291 | // Commas must not follow "[", "{", or ","
292 | requireNonComma = open || comma;
293 |
294 | // Determine new depth
295 | // array/object open ("[" or "{"): depth += true - false (increment)
296 | // array/object close ("]" or "}"): depth += false - true (decrement)
297 | // other cases ("," or primitive): depth += true - true (numeric cast)
298 | depth += !close - !open;
299 |
300 | // Remove this token
301 | return '';
302 | }) ) ?
303 | ( Function( 'return ' + data ) )() :
304 | false;
305 | };
306 |
307 | /**
308 | * Accepts a jquery object, a string containing an element ID, or an element,
309 | * verifies that it exists, and returns the element.
310 | * @param {Mixed} elem
311 | * @return {Element}
312 | */
313 | sc.verifyElem = function( elem ) {
314 | "use strict";
315 |
316 | if ( typeof jQuery !== 'undefined' && elem instanceof jQuery ) {
317 | elem = elem[0];
318 |
319 | } else if ( typeof elem === 'string' ) {
320 | if ( elem.charAt( 0 ) == '#' ) {
321 | elem = elem.substr( 1 );
322 | }
323 | elem = document.getElementById( elem );
324 | }
325 |
326 | if ( !elem || elem.nodeType !== 1 ) {
327 | return false;
328 | }
329 |
330 | if ( elem.nodeName.toUpperCase() == 'A' ) {
331 | elem.style.cursor = 'pointer';
332 |
333 | sc.addEvent( elem, 'click', function( e ) {
334 | if ( e && e.preventDefault ) {
335 | e.preventDefault();
336 |
337 | } else if ( window.event ) {
338 | window.event.returnValue = false;
339 | }
340 | });
341 | }
342 |
343 | return elem;
344 | };
345 |
346 |
347 | /**
348 | * @constructor
349 | * @param {Object} options
350 | */
351 | sc.SpellChecker = function( options ) {
352 |
353 | var self = this;
354 |
355 | this._settings = {
356 | action: '', // URL of server script
357 | button: '', // Button that opens spell checker
358 | textInput: '', // Text input to spell check
359 | name: 'text', // Parameter name of text sent to server
360 | data: {}, // Additional data to send to the server (optional)
361 | debug: false,
362 | onOpen: function( button, text ) {}, // Callback to be executed when spell checker is opened
363 | onClose: function( button, text ) {} // Callback to be executed after spell checker is closed
364 | };
365 |
366 | sc.extendObj( this._settings, options );
367 |
368 | this._button = sc.verifyElem( this._settings.button );
369 | this._textInput = sc.verifyElem( this._settings.textInput );
370 |
371 | delete this._settings.button;
372 |
373 | if ( this._button === false ) {
374 | throw new Error( "Invalid button. Make sure the element you're passing exists." );
375 | }
376 |
377 | if ( this._textInput === false ) {
378 | throw new Error( "Invalid text field. Make sure the element you're passing exists." );
379 | }
380 |
381 | this._closeOnEsc = function( event ) {
382 | if ( event.keyCode === 27 ) {
383 | self._closeChecker();
384 | }
385 | };
386 |
387 | this._uId = sc.getId();
388 | this._createHTML();
389 |
390 | this._currentBox = _( 'spell-current' + this._uId );
391 | this._contextBox = _( 'spell-context' + this._uId );
392 | this._undoBtn = _( 'spell-undo' + this._uId );
393 | this._select = _( 'spelling-suggestions' + this._uId );
394 |
395 | this._isOpen = false;
396 |
397 | // Add CSS class to button for pointer cursor when hovering
398 | this._button.className += ' spellcheck-trigger';
399 |
400 | this.enable();
401 | };
402 |
403 | sc.SpellChecker.prototype = {
404 |
405 | enable: function() {
406 | "use strict";
407 |
408 | var self = this;
409 |
410 | this._button.off = sc.addEvent( this._button, 'click', function() {
411 | self._openChecker();
412 | });
413 |
414 | sc.addEvent( 'spelling-ignore' + this._uId, 'click', function() {
415 | self._ignore();
416 | });
417 |
418 | sc.addEvent( 'spelling-ignore-all' + this._uId, 'click', function() {
419 | self._ignore( true );
420 | });
421 |
422 | sc.addEvent( 'spell-change' + this._uId, 'click', function() {
423 | self._makeChange();
424 | });
425 |
426 | sc.addEvent( 'spell-change-all' + this._uId, 'click', function() {
427 | self._makeChange( true );
428 | });
429 |
430 | sc.addEvent( 'spell-close' + this._uId, 'click', function() {
431 | self._closeChecker();
432 | });
433 |
434 | sc.addEvent( 'spell-msg-close' + this._uId, 'click', function() {
435 | self._closeChecker();
436 | });
437 |
438 | sc.addEvent( 'spell-check-overlay' + this._uId, 'click', function() {
439 | self._closeChecker();
440 | });
441 |
442 | sc.addEvent( this._undoBtn, 'click', function() {
443 | self._undoChange();
444 | });
445 |
446 | // Unselect any suggestion if user clicks either input so that word is
447 | // changed to correct spelling
448 | sc.addEvent( this._currentBox, 'click', function() {
449 | _( 'spelling-suggestions' + self._uId ).selectedIndex = -1;
450 | });
451 |
452 | sc.addEvent( this._contextBox, 'click', function() {
453 | _( 'spelling-suggestions' + self._uId ).selectedIndex = -1;
454 | });
455 |
456 | // Change "Not found in dictionary" if user edits within word context box
457 | sc.addEvent( this._currentBox, 'keyup', function() {
458 | var span = this._contextBox.getElementsByTagName( 'span' )[0];
459 | if ( span && span.firstChild ) {
460 | span.firstChild.nodeValue = this.value;
461 | }
462 | });
463 |
464 | // Change word context also if user edits "Not found in dictionary" box
465 | sc.addEvent( this._contextBox, 'keyup', function() {
466 | var span = this.getElementsByTagName( 'span' )[0];
467 | if ( span && span.firstChild ) {
468 | this._currentBox.value = span.firstChild.nodeValue;
469 | }
470 | });
471 | },
472 |
473 | /**
474 | * Completely removes spell check functionality
475 | */
476 | destroy: function() {
477 | "use strict";
478 |
479 | // Close the checker if it's open
480 | if ( this._isOpen ) {
481 | this._closeChecker();
482 | }
483 |
484 | // Remove event listener from button
485 | if ( this._button.off ) {
486 | this._button.off();
487 | }
488 |
489 | // Remove .spellcheck-trigger CSS class from button
490 | this._button.className = this._button.className.replace( /(?:^|\s)spellcheck-trigger(?!\S)/ , '' );
491 |
492 | // Remove all of the HTML we created
493 | sc.remove( this._msgBox );
494 | sc.remove( this._modal );
495 | sc.remove( this._overlay );
496 |
497 | // Now burn it all down
498 | for ( var prop in this ) {
499 | if ( this.hasOwnProperty( prop ) ) {
500 | delete this.prop;
501 | }
502 | }
503 | },
504 |
505 | /**
506 | * Send data to browser console if debug is set to true
507 | */
508 | log: function( str ) {
509 | "use strict";
510 |
511 | if ( this._settings.debug && window.console ) {
512 | window.console.log( '[spell checker] ' + str );
513 | }
514 | },
515 |
516 | _getRegex: function( word ) {
517 | "use strict";
518 |
519 | if ( !regexCache[word] ) {
520 | regexCache[word] = new RegExp( word, 'g' );
521 | }
522 | return regexCache[word];
523 | },
524 |
525 | /**
526 | * Begins the spell check function.
527 | */
528 | _openChecker: function() {
529 | "use strict";
530 |
531 | if ( this._isOpen ) {
532 | return;
533 | }
534 |
535 | this._undoBtn.disabled = true;
536 | this._overlay.style.display = 'block';
537 | this._modal.style.display = 'block';
538 |
539 | // Get the text that we're going to spell check
540 | this._text = this._textInput.value;
541 | this._isOpen = true;
542 |
543 | // Array of objects containing change history for "Undo"
544 | this._undo = [];
545 |
546 | // Add listener for escape key to close checker
547 | sc.addEvent( document, 'keyup', this._closeOnEsc );
548 |
549 | // Show "Checking..." message
550 | this._notifyMsg( 'a' );
551 |
552 | // Send the text to the server
553 | this._sendData();
554 | },
555 |
556 | /**
557 | * Closes the spell check box and cleans up.
558 | */
559 | _closeChecker: function() {
560 | "use strict";
561 |
562 | if ( !this._isOpen ) {
563 | return;
564 | }
565 |
566 | // Close all dialog boxes
567 | this._msgBox.style.display = 'none';
568 | this._modal.style.cssText = 'display:none; z-index:4999;';
569 | this._overlay.style.display = 'none';
570 | this._currentBox.value = '';
571 | this._contextBox.innerHTML = '';
572 | this._select.options.length = 0;
573 | this._undo.length = 0;
574 |
575 | // Reset everything after finishing
576 | this._text = this._wordObject = this._wordKeys = this._currentWord = this._wordMatches = this._matchOffset = this._undo = this._isOpen = null;
577 |
578 | // Removes listener for escape key
579 | sc.removeEvent( document, 'keyup', this._closeOnEsc );
580 |
581 | this._settings.onClose.call( this, this._button, this._textInput.value );
582 | },
583 |
584 | /**
585 | * Provides user with status messages.
586 | */
587 | _notifyMsg: function( type ) {
588 | "use strict";
589 |
590 | var msg,
591 | closeBox = _( 'spell-msg-close-box' + this._uId );
592 |
593 | if ( type == 'a' ) {
594 | msg = 'Checking...';
595 | closeBox.style.display = 'none';
596 | } else {
597 | closeBox.style.display = 'block';
598 | }
599 |
600 | if ( type == 'b' ) {
601 | msg = 'We experienced an error and were unable to complete the spell check.';
602 | }
603 |
604 | if ( type == 'c' ) {
605 | msg = 'Spell check completed. No errors found.';
606 | }
607 |
608 | if ( type == 'd' ) {
609 | msg = 'Spell check completed.';
610 | }
611 |
612 | // Put the spell check box behind the message box
613 | this._modal.style.zIndex = 4999;
614 |
615 | // Inject the correct message
616 | _( 'spell-msg-text' + this._uId ).innerHTML = msg;
617 |
618 | // Make the message box visible
619 | this._msgBox.style.display = 'block';
620 |
621 | // Focus on "OK" button if anything but "Checking..." message
622 | if ( type != 'a' ) {
623 | _( 'spell-msg-close' + this._uId ).focus();
624 | }
625 | },
626 |
627 | /**
628 | * Ignores the potentially misspelled word currently in review
629 | */
630 | _ignore: function( ignoreAll ) {
631 | "use strict";
632 |
633 | var moreMatches;
634 |
635 | if ( ignoreAll === true ||
636 | this._wordMatches <= 1 ||
637 | this._matchOffset === this._wordMatches )
638 | {
639 | this._wordKeys.splice( 0, 1 );
640 | this._matchOffset = 1; // Reset to 1 in case there is another word to review
641 | moreMatches = false;
642 | } else {
643 | // Increment the match counter because we're using the same word next round
644 | // This prevents us from reviewing the same occurrence of this word
645 | this._matchOffset++;
646 | moreMatches = true; // There are remaining duplicates of this word to review
647 | }
648 |
649 | // Disable "Undo" in case the prior action was a change
650 | this._undoBtn.disabled = true;
651 |
652 | // Empty the change history array to help keep it under control
653 | this._undo.length = 0;
654 |
655 | if ( this._wordKeys.length > 0 || moreMatches === true ) {
656 | // Continue working if that wasn't the last word
657 | this._reviewWord();
658 | } else {
659 | this._notifyMsg( 'd' );
660 | }
661 | },
662 |
663 | /**
664 | * Changes the misspelled word currently in review
665 | */
666 | _makeChange: function( changeAll ) {
667 | "use strict";
668 |
669 | var self = this,
670 | regex = this._getRegex( this._currentWord ),
671 | selected_option = this._select.selectedIndex,
672 | m = 0,
673 | new_word,
674 | new_text,
675 | moreMatches;
676 |
677 | // Save the current state before we change anything
678 | this._undo.unshift({
679 | text: this._text,
680 | word: this._currentWord,
681 | numMatches: this._wordMatches,
682 | matchOffset: this._matchOffset
683 | });
684 |
685 | // Enable the "Undo" button
686 | this._undoBtn.disabled = false;
687 |
688 | if ( selected_option > -1 ) {
689 | new_word = this._select.options[selected_option].text; // Use suggestion if one is selected
690 | } else {
691 | new_word = this._currentBox.value;
692 | }
693 |
694 | // Replace misspelled word with new word
695 | new_text = this._text.replace( regex, function( match ) {
696 | m++;
697 |
698 | // Replace if we've landed on the right occurrence or it's "Change All"
699 | if ( changeAll === true || self._matchOffset === m ) {
700 | return new_word;
701 | }
702 |
703 | // Otherwise don't change this occurrence
704 | return match;
705 | });
706 |
707 | // Only remove the replaced word if we won't need it again
708 | if ( changeAll === true ||
709 | self._wordMatches <= 1 ||
710 | self._matchOffset === self._wordMatches )
711 | {
712 | // Remove word from our list b/c we're finished with it
713 | this._wordKeys.splice( 0, 1 );
714 |
715 | // Reset to 1 in case there is another word to review
716 | this._matchOffset = 1;
717 |
718 | // No remaining duplicates of this word
719 | moreMatches = false;
720 |
721 | // There are remaining duplicates of this word to review
722 | } else {
723 | moreMatches = true;
724 | }
725 |
726 | // Update text with new version
727 | this._textInput.value = this._text = new_text;
728 |
729 | // Keep going if there are more words to review
730 | if ( this._wordKeys.length > 0 || moreMatches === true ) {
731 | this._reviewWord();
732 |
733 | // Otherwise do "Spell check completed"
734 | } else {
735 | this._notifyMsg( 'd' );
736 | }
737 | },
738 |
739 | /**
740 | * Undo the previous change action
741 | */
742 | _undoChange: function() {
743 | "use strict";
744 |
745 | var prevData = this._undo[0];
746 |
747 | // Restore text to pre-change state
748 | this._textInput.value = this._text = prevData.text;
749 |
750 | // Return previous word to the "Not found in dictionary" field
751 | this._currentBox.value = prevData.word;
752 |
753 | // Add previous word back to beginning of array if it was removed
754 | if ( !sc.contains( this._wordKeys, prevData.word ) ) {
755 | this._wordKeys.unshift( prevData.word );
756 | }
757 |
758 | // Restore variables to their value prior to change
759 | this._currentWord = prevData.word;
760 | this._wordMatches = prevData.numMatches;
761 | this._matchOffset = prevData.matchOffset;
762 |
763 | // Populate suggestion box with options
764 | this._setSuggestionOptions();
765 |
766 | // Reset the word context box
767 | this._setContextBox();
768 |
769 | // Remove from change history array
770 | this._undo.splice( 0, 1 );
771 |
772 | // Disable "Undo" button if no more changes to undo
773 | if ( this._undo.length < 1 ) {
774 | this._undoBtn.disabled = true;
775 | }
776 | },
777 |
778 | /**
779 | * Populates the spelling suggestions select box with options
780 | */
781 | _setSuggestionOptions: function() {
782 | "use strict";
783 |
784 | var suggestions = this._wordObject[this._currentWord],
785 | num = suggestions.length,
786 | i;
787 |
788 | // Clear out any existing options
789 | this._select.options.length = 0;
790 |
791 | for ( i = 0; i < num; i++ ) {
792 | this._select.options[i] = new Option( suggestions[i], suggestions[i] );
793 | }
794 |
795 | // Select the first suggestion option
796 | this._select.selectedIndex = 0;
797 | },
798 |
799 | /**
800 | * Places the misspelled word in the review box along with surrounding words for context
801 | */
802 | _setContextBox: function() {
803 | "use strict";
804 |
805 | var self = this,
806 | wordLength = this._currentWord.length,
807 | regex = this._getRegex( this._currentWord ),
808 | textLength = this._text.length,
809 | i = 0;
810 |
811 | this._text.replace( regex, function( match, index ) {
812 | // Prevents false matches for substring of a word. Ex: 'pre' matching 'previous'
813 | // Text is split by alphanumeric chars, so if the next char is alphanumeric, it's a false match
814 | if ( rAlphaNum.test( self._text.substr( index + wordLength, 1 ) ) ) {
815 | return match;
816 | }
817 |
818 | i++;
819 |
820 | if ( i === self._matchOffset ) {
821 | var firstHalf,
822 | secondHalf,
823 | startFirstHalf = index - 20,
824 | startSecondHalf = index + wordLength;
825 |
826 | if ( startFirstHalf < 0 ) {
827 | firstHalf = self._text.substr( 0, index );
828 | } else {
829 | firstHalf = self._text.substr( startFirstHalf, 20 );
830 | }
831 |
832 | if ( startSecondHalf + 50 > textLength ) {
833 | secondHalf = self._text.substr( startSecondHalf );
834 | } else {
835 | secondHalf = self._text.substr( startSecondHalf, 50 );
836 | }
837 |
838 | // This prevents broken words from going into the sentence context box by
839 | // trimming whitespace, trimming non-white space, then trimming white space again.
840 | firstHalf = firstHalf.replace( rLWhitespace, '' )
841 | .replace( rLNonWhitespace, '' )
842 | .replace( rLWhitespace, '' );
843 |
844 | secondHalf = secondHalf.replace( rTWhitespace, '' )
845 | .replace( rRNonWhitespace, '' )
846 | .replace( rTWhitespace, '' );
847 |
848 | self._contextBox.innerHTML = sc.encodeHTML( firstHalf ) +
849 | '
' +
850 | sc.encodeHTML( self._currentWord ) +
851 | ' ' +
852 | sc.encodeHTML( secondHalf );
853 | }
854 |
855 | return match;
856 | });
857 | },
858 |
859 | /**
860 | * Begin resolving a potentially misspelled word
861 | *
862 | * Executes at beginning of spell check if the server reports spelling errors or
863 | * after resolving the last word and moving to the next.
864 | */
865 | _reviewWord: function() {
866 | "use strict";
867 |
868 | // The misspelled word currently being reviewed
869 | // (always the first element of the keys array)
870 | this._currentWord = this._wordKeys[0];
871 |
872 | this._currentBox.value = this._currentWord;
873 |
874 | // Find how many occurrences of the misspelled word so each one is reviewed
875 | this._wordMatches = this._getTotalWordMatches();
876 |
877 | // Populate select field with spelling suggestion options
878 | this._setSuggestionOptions();
879 |
880 | // Place misspelled word in review box with leading and trailing words for context
881 | this._setContextBox();
882 | },
883 |
884 | /**
885 | * Counts number of occurrences of the misspelled word so each will be reviewed
886 | */
887 | _getTotalWordMatches: function() {
888 | "use strict";
889 |
890 | var regex = this._getRegex( this._currentWord ),
891 | wordLength = this._currentWord.length,
892 | matches = 0,
893 | text = this._text;
894 |
895 | // Search through text for each occurrence of the misspelled word
896 | // Only count matches where next character is NOT alphanumeric
897 | // Prevents false matches for substring of a word. Ex: 'pre' matching 'previous'
898 | this._text.replace( regex, function( match, index ) {
899 | if ( !rAlphaNum.test( text.substr( index + wordLength, 1 ) ) ) {
900 | matches++;
901 | }
902 | return match;
903 | });
904 |
905 | return matches;
906 | },
907 |
908 | /**
909 | * Begins spell check process after data has been received from server
910 | */
911 | _begin: function( response ) {
912 | "use strict";
913 |
914 | if ( response.success && response.success === true ) {
915 |
916 | // Open the review box if there were spelling errors found
917 | if ( response.errors && response.errors === true ) {
918 | this._wordObject = response.words;
919 | this._wordKeys = sc.objectKeys( this._wordObject );
920 | this._matchOffset = 1;
921 |
922 | this._msgBox.style.display = 'none';
923 | this._modal.style.zIndex = 5001;
924 | this._reviewWord();
925 |
926 | // Otherwise do "Spell check completed. No errors found." message
927 | } else {
928 | this._notifyMsg( 'c' );
929 | }
930 | }
931 | },
932 |
933 | /**
934 | * Sends text to the server for spell review
935 | */
936 | _sendData: function() {
937 | "use strict";
938 |
939 | var self = this,
940 | xhr = sc.newXHR(),
941 | data,
942 | callback;
943 |
944 | // Don't waste a server request for less than 2 characters
945 | if ( this._text.length < 2 ) {
946 | // Do "Spell check completed. No errors found" message
947 | this._notifyMsg( 'c' );
948 | return;
949 | }
950 |
951 | data = encodeURIComponent( this._settings.name ) + '=';
952 | data += encodeURIComponent( this._text );
953 |
954 | // Add any additional data
955 | if ( !sc.isEmpty( this._settings.data ) ) {
956 | data += '&';
957 | data += sc.obj2string( this._settings.data );
958 | }
959 |
960 | callback = function() {
961 | var response,
962 | status,
963 | statusText;
964 |
965 | try {
966 | if ( callback && xhr.readyState === 4 ) {
967 | callback = undefined;
968 | xhr.onreadystatechange = function() {};
969 |
970 | // Only continue if the spell checker is open. This way, closing the checker
971 | // before the request is completed effectively aborts the request
972 | if ( !self._isOpen ) {
973 | return;
974 | }
975 |
976 | status = xhr.status;
977 |
978 | try {
979 | statusText = xhr.statusText;
980 | } catch( e ) {
981 | statusText = '';
982 | }
983 |
984 | self.log( 'Request completed. Status: ' + status + ' ' + statusText );
985 |
986 | if ( status >= 200 && status < 300 ) {
987 | response = sc.parseJSON( xhr.responseText );
988 |
989 | if ( response !== false ) {
990 | self._settings.onOpen.call( self, self._button, self._text );
991 | self._begin( response );
992 |
993 | // There was an error parsing the server response
994 | } else {
995 | self.log( 'Error parsing server response' );
996 | self._notifyMsg( 'b' );
997 | }
998 |
999 | xhr = response = null;
1000 |
1001 | // We didn't get a 2xx status
1002 | } else {
1003 | self._notifyMsg( 'b' );
1004 | }
1005 |
1006 | }
1007 |
1008 | } catch( e ) {
1009 | self.log( 'Error: ' + e.message );
1010 | self._notifyMsg( 'b' );
1011 | }
1012 | };
1013 |
1014 | xhr.onreadystatechange = callback;
1015 | xhr.open( 'POST', this._settings.action, true );
1016 | xhr.setRequestHeader( 'Accept', 'application/json, text/javascript, */*; q=0.01' );
1017 | xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
1018 | xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
1019 | self.log( 'Sending data...' );
1020 | xhr.send( data );
1021 | },
1022 |
1023 | /**
1024 | * Creates HTML for spell checker
1025 | */
1026 | _createHTML: function() {
1027 | this._overlay = document.createElement( 'div' );
1028 | this._modal = document.createElement( 'div' );
1029 | this._msgBox = document.createElement( 'div' );
1030 |
1031 | // Screen overlay
1032 | this._overlay.className = 'spell-check-overlay';
1033 | this._overlay.id = 'spell-check-overlay' + this._uId;
1034 | document.body.appendChild( this._overlay );
1035 |
1036 | // Spell check box
1037 | this._modal.className = 'spell-wrap';
1038 | this._modal.innerHTML = '
Not found in dictionary:
Ignore Ignore All
Suggestions:
Change Change All Undo
Close
';
1039 | document.body.appendChild( this._modal );
1040 |
1041 | // Popup message box
1042 | this._msgBox.className = 'spell-msg';
1043 | this._msgBox.innerHTML = '
OK
';
1044 | document.body.appendChild( this._msgBox );
1045 | }
1046 | };
1047 |
1048 | // Expose to the global window object
1049 | window.sc = sc;
1050 |
1051 | })( window, document );
--------------------------------------------------------------------------------
/js/spellcheck.min.js:
--------------------------------------------------------------------------------
1 | /** Javascript/PHP Spell Checker v1.6.1 https://github.com/LPology/Javascript-PHP-Spell-Checker Copyright 2012-2015 LPology, LLC Released under the MIT license */
2 | !function(t,e,s){var n=t.sc||{},i=/^\s+/,o=/\s+$/,r=/[^\s]+/,c=/[^\s]+$/,l=/[xy]/g,u=/&/g,h=/"/g,d=/'/g,a=//g,p=/^\w+$/,g={},f=function(t){return e.getElementById(t)};n.objectKeys=function(t){"use strict";var e=[];for(var s in t)t.hasOwnProperty(s)&&e.push(s);return e},n.obj2string=function(t,e){"use strict";var s=[];for(var i in t)if(t.hasOwnProperty(i)){var o=e?e+"["+i+"]":i,r=t[i];s.push("object"==typeof r?n.obj2string(r,o):encodeURIComponent(o)+"="+encodeURIComponent(r))}return s.join("&")},n.extendObj=function(t,e){"use strict";for(var s in e)e.hasOwnProperty(s)&&(t[s]=e[s])},n.isEmpty=function(t){"use strict";for(var e in t)if(t.hasOwnProperty(e))return!1;return!0},n.contains=function(t,e){"use strict";for(var s=t.length;s--;)if(t[s]===e)return!0;return!1},n.purge=function(t){"use strict";var e,s,i,o=t.attributes;if(o)for(e=o.length-1;e>=0;e-=1)i=o[e].name,"function"==typeof t[i]&&(t[i]=null);if(o=t.childNodes)for(s=o.length,e=0;s>e;e+=1)n.purge(t.childNodes[e])},n.remove=function(t){"use strict";t&&t.parentNode&&(n.purge(t),t.parentNode.removeChild(t)),t=null};var v="".trim;n.trim=v&&!v.call("\ufeff ")?function(t){return null===t?"":v.call(t)}:function(t){return null===t?"":t.toString().replace(i,"").replace(o,"")},n.getId=function(){"use strict";return"axxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(l,function(t){var e=16*Math.random()|0,s="x"==t?e:3&e|8;return s.toString(16)})},n.addEvent=function(t,s,i){"use strict";return"string"==typeof t&&(t=e.getElementById(t)),t.addEventListener?t.addEventListener(s,i,!1):t.attachEvent("on"+s,i),function(){n.removeEvent(t,s,i)}},n.removeEvent=function(t,s,n){"use strict";"string"==typeof t&&(t=e.getElementById(t)),t.removeEventListener?t.removeEventListener(s,n,!1):t.detachEvent("on"+s,n)},n.newXHR=function(){"use strict";if("undefined"!=typeof XMLHttpRequest)return new t.XMLHttpRequest;if(t.ActiveXObject)try{return new t.ActiveXObject("Microsoft.XMLHTTP")}catch(e){return!1}},n.encodeHTML=function(t){"use strict";return String(t).replace(u,"&").replace(h,""").replace(d,"'").replace(a,"<").replace(_,">")},n.parseJSON=function(e){"use strict";if(!e)return!1;if(e=n.trim(e+""),t.JSON&&t.JSON.parse)try{return t.JSON.parse(e+"")}catch(s){return!1}var i,o=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g,r=null;return e&&!n.trim(e.replace(o,function(t,e,s,n){return i&&e&&(r=0),0===r?t:(i=s||e,r+=!n-!s,"")}))?Function("return "+e)():!1},n.verifyElem=function(s){"use strict";return"undefined"!=typeof jQuery&&s instanceof jQuery?s=s[0]:"string"==typeof s&&("#"==s.charAt(0)&&(s=s.substr(1)),s=e.getElementById(s)),s&&1===s.nodeType?("A"==s.nodeName.toUpperCase()&&(s.style.cursor="pointer",n.addEvent(s,"click",function(e){e&&e.preventDefault?e.preventDefault():t.event&&(t.event.returnValue=!1)})),s):!1},n.SpellChecker=function(t){var e=this;if(this._settings={action:"",button:"",textInput:"",name:"text",data:{},debug:!1,onOpen:function(){},onClose:function(){}},n.extendObj(this._settings,t),this._button=n.verifyElem(this._settings.button),this._textInput=n.verifyElem(this._settings.textInput),delete this._settings.button,this._button===!1)throw new Error("Invalid button. Make sure the element you're passing exists.");if(this._textInput===!1)throw new Error("Invalid text field. Make sure the element you're passing exists.");this._closeOnEsc=function(t){27===t.keyCode&&e._closeChecker()},this._uId=n.getId(),this._createHTML(),this._currentBox=f("spell-current"+this._uId),this._contextBox=f("spell-context"+this._uId),this._undoBtn=f("spell-undo"+this._uId),this._select=f("spelling-suggestions"+this._uId),this._isOpen=!1,this._button.className+=" spellcheck-trigger",this.enable()},n.SpellChecker.prototype={enable:function(){"use strict";var t=this;this._button.off=n.addEvent(this._button,"click",function(){t._openChecker()}),n.addEvent("spelling-ignore"+this._uId,"click",function(){t._ignore()}),n.addEvent("spelling-ignore-all"+this._uId,"click",function(){t._ignore(!0)}),n.addEvent("spell-change"+this._uId,"click",function(){t._makeChange()}),n.addEvent("spell-change-all"+this._uId,"click",function(){t._makeChange(!0)}),n.addEvent("spell-close"+this._uId,"click",function(){t._closeChecker()}),n.addEvent("spell-msg-close"+this._uId,"click",function(){t._closeChecker()}),n.addEvent("spell-check-overlay"+this._uId,"click",function(){t._closeChecker()}),n.addEvent(this._undoBtn,"click",function(){t._undoChange()}),n.addEvent(this._currentBox,"click",function(){f("spelling-suggestions"+t._uId).selectedIndex=-1}),n.addEvent(this._contextBox,"click",function(){f("spelling-suggestions"+t._uId).selectedIndex=-1}),n.addEvent(this._currentBox,"keyup",function(){var t=this._contextBox.getElementsByTagName("span")[0];t&&t.firstChild&&(t.firstChild.nodeValue=this.value)}),n.addEvent(this._contextBox,"keyup",function(){var t=this.getElementsByTagName("span")[0];t&&t.firstChild&&(this._currentBox.value=t.firstChild.nodeValue)})},destroy:function(){"use strict";this._isOpen&&this._closeChecker(),this._button.off&&this._button.off(),this._button.className=this._button.className.replace(/(?:^|\s)spellcheck-trigger(?!\S)/,""),n.remove(this._msgBox),n.remove(this._modal),n.remove(this._overlay);for(var t in this)this.hasOwnProperty(t)&&delete this.prop},log:function(e){"use strict";this._settings.debug&&t.console&&t.console.log("[spell checker] "+e)},_getRegex:function(t){"use strict";return g[t]||(g[t]=new RegExp(t,"g")),g[t]},_openChecker:function(){"use strict";this._isOpen||(this._undoBtn.disabled=!0,this._overlay.style.display="block",this._modal.style.display="block",this._text=this._textInput.value,this._isOpen=!0,this._undo=[],n.addEvent(e,"keyup",this._closeOnEsc),this._notifyMsg("a"),this._sendData())},_closeChecker:function(){"use strict";this._isOpen&&(this._msgBox.style.display="none",this._modal.style.cssText="display:none; z-index:4999;",this._overlay.style.display="none",this._currentBox.value="",this._contextBox.innerHTML="",this._select.options.length=0,this._undo.length=0,this._text=this._wordObject=this._wordKeys=this._currentWord=this._wordMatches=this._matchOffset=this._undo=this._isOpen=null,n.removeEvent(e,"keyup",this._closeOnEsc),this._settings.onClose.call(this,this._button,this._textInput.value))},_notifyMsg:function(t){"use strict";var e,s=f("spell-msg-close-box"+this._uId);"a"==t?(e="Checking...",s.style.display="none"):s.style.display="block","b"==t&&(e="We experienced an error and were unable to complete the spell check."),"c"==t&&(e="Spell check completed. No errors found."),"d"==t&&(e="Spell check completed."),this._modal.style.zIndex=4999,f("spell-msg-text"+this._uId).innerHTML=e,this._msgBox.style.display="block","a"!=t&&f("spell-msg-close"+this._uId).focus()},_ignore:function(t){"use strict";var e;t===!0||this._wordMatches<=1||this._matchOffset===this._wordMatches?(this._wordKeys.splice(0,1),this._matchOffset=1,e=!1):(this._matchOffset++,e=!0),this._undoBtn.disabled=!0,this._undo.length=0,this._wordKeys.length>0||e===!0?this._reviewWord():this._notifyMsg("d")},_makeChange:function(t){"use strict";var e,s,n,i=this,o=this._getRegex(this._currentWord),r=this._select.selectedIndex,c=0;this._undo.unshift({text:this._text,word:this._currentWord,numMatches:this._wordMatches,matchOffset:this._matchOffset}),this._undoBtn.disabled=!1,e=r>-1?this._select.options[r].text:this._currentBox.value,s=this._text.replace(o,function(s){return c++,t===!0||i._matchOffset===c?e:s}),t===!0||i._wordMatches<=1||i._matchOffset===i._wordMatches?(this._wordKeys.splice(0,1),this._matchOffset=1,n=!1):n=!0,this._textInput.value=this._text=s,this._wordKeys.length>0||n===!0?this._reviewWord():this._notifyMsg("d")},_undoChange:function(){"use strict";var t=this._undo[0];this._textInput.value=this._text=t.text,this._currentBox.value=t.word,n.contains(this._wordKeys,t.word)||this._wordKeys.unshift(t.word),this._currentWord=t.word,this._wordMatches=t.numMatches,this._matchOffset=t.matchOffset,this._setSuggestionOptions(),this._setContextBox(),this._undo.splice(0,1),this._undo.length<1&&(this._undoBtn.disabled=!0)},_setSuggestionOptions:function(){"use strict";var t,e=this._wordObject[this._currentWord],s=e.length;for(this._select.options.length=0,t=0;s>t;t++)this._select.options[t]=new Option(e[t],e[t]);this._select.selectedIndex=0},_setContextBox:function(){"use strict";var t=this,e=this._currentWord.length,s=this._getRegex(this._currentWord),l=this._text.length,u=0;this._text.replace(s,function(s,h){if(p.test(t._text.substr(h+e,1)))return s;if(u++,u===t._matchOffset){var d,a,_=h-20,g=h+e;d=0>_?t._text.substr(0,h):t._text.substr(_,20),a=g+50>l?t._text.substr(g):t._text.substr(g,50),d=d.replace(i,"").replace(r,"").replace(i,""),a=a.replace(o,"").replace(c,"").replace(o,""),t._contextBox.innerHTML=n.encodeHTML(d)+'
'+n.encodeHTML(t._currentWord)+" "+n.encodeHTML(a)}return s})},_reviewWord:function(){"use strict";this._currentWord=this._wordKeys[0],this._currentBox.value=this._currentWord,this._wordMatches=this._getTotalWordMatches(),this._setSuggestionOptions(),this._setContextBox()},_getTotalWordMatches:function(){"use strict";var t=this._getRegex(this._currentWord),e=this._currentWord.length,s=0,n=this._text;return this._text.replace(t,function(t,i){return p.test(n.substr(i+e,1))||s++,t}),s},_begin:function(t){"use strict";t.success&&t.success===!0&&(t.errors&&t.errors===!0?(this._wordObject=t.words,this._wordKeys=n.objectKeys(this._wordObject),this._matchOffset=1,this._msgBox.style.display="none",this._modal.style.zIndex=5001,this._reviewWord()):this._notifyMsg("c"))},_sendData:function(){"use strict";var t,e,i=this,o=n.newXHR();return this._text.length<2?void this._notifyMsg("c"):(t=encodeURIComponent(this._settings.name)+"=",t+=encodeURIComponent(this._text),n.isEmpty(this._settings.data)||(t+="&",t+=n.obj2string(this._settings.data)),e=function(){var t,r,c;try{if(e&&4===o.readyState){if(e=s,o.onreadystatechange=function(){},!i._isOpen)return;r=o.status;try{c=o.statusText}catch(l){c=""}i.log("Request completed. Status: "+r+" "+c),r>=200&&300>r?(t=n.parseJSON(o.responseText),t!==!1?(i._settings.onOpen.call(i,i._button,i._text),i._begin(t)):(i.log("Error parsing server response"),i._notifyMsg("b")),o=t=null):i._notifyMsg("b")}}catch(l){i.log("Error: "+l.message),i._notifyMsg("b")}},o.onreadystatechange=e,o.open("POST",this._settings.action,!0),o.setRequestHeader("Accept","application/json, text/javascript, */*; q=0.01"),o.setRequestHeader("X-Requested-With","XMLHttpRequest"),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),i.log("Sending data..."),void o.send(t))},_createHTML:function(){this._overlay=e.createElement("div"),this._modal=e.createElement("div"),this._msgBox=e.createElement("div"),this._overlay.className="spell-check-overlay",this._overlay.id="spell-check-overlay"+this._uId,e.body.appendChild(this._overlay),this._modal.className="spell-wrap",this._modal.innerHTML='
Not found in dictionary:
Ignore Ignore All
Suggestions:
Change Change All Undo
Close
',e.body.appendChild(this._modal),this._msgBox.className="spell-msg",this._msgBox.innerHTML='
OK
',e.body.appendChild(this._msgBox)}},t.sc=n}(window,document);
3 |
--------------------------------------------------------------------------------
/spellcheck.php:
--------------------------------------------------------------------------------
1 | false)));
19 | }
20 |
21 | if (!$pspell = pspell_new('en', '', '', '', PSPELL_FAST)) {
22 | exit(json_encode(array('success' => false)));
23 | }
24 |
25 | $words = preg_split('/[\W]+/u', $text, -1, PREG_SPLIT_NO_EMPTY);
26 | $misspelled = array();
27 | $return = array();
28 |
29 | foreach ($words as $w) {
30 | if (!pspell_check($pspell, $w) && !is_numeric($w)) {
31 | $misspelled[] = $w;
32 | }
33 | }
34 |
35 | if (sizeof($misspelled) < 1) {
36 | exit(json_encode(array('success' => true, 'errors' => false)));
37 | }
38 |
39 | foreach ($misspelled as $m) {
40 | $return[$m] = pspell_suggest($pspell, $m);
41 | }
42 |
43 | echo json_encode(array('success' => true, 'errors' => true, 'words' => $return));
44 |
--------------------------------------------------------------------------------