web/wp-includes/js/tinymce/plugins/spellchecker/editor_plugin_src.js
changeset 194 32102edaa81b
child 204 09a1c134465b
equal deleted inserted replaced
193:2f6f6f7551ca 194:32102edaa81b
       
     1 /**
       
     2  * editor_plugin_src.js
       
     3  *
       
     4  * Copyright 2009, Moxiecode Systems AB
       
     5  * Released under LGPL License.
       
     6  *
       
     7  * License: http://tinymce.moxiecode.com/license
       
     8  * Contributing: http://tinymce.moxiecode.com/contributing
       
     9  */
       
    10 
       
    11 (function() {
       
    12 	var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM;
       
    13 
       
    14 	tinymce.create('tinymce.plugins.SpellcheckerPlugin', {
       
    15 		getInfo : function() {
       
    16 			return {
       
    17 				longname : 'Spellchecker',
       
    18 				author : 'Moxiecode Systems AB',
       
    19 				authorurl : 'http://tinymce.moxiecode.com',
       
    20 				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker',
       
    21 				version : tinymce.majorVersion + "." + tinymce.minorVersion
       
    22 			};
       
    23 		},
       
    24 
       
    25 		init : function(ed, url) {
       
    26 			var t = this, cm;
       
    27 
       
    28 			t.url = url;
       
    29 			t.editor = ed;
       
    30 			t.rpcUrl = ed.getParam("spellchecker_rpc_url", "{backend}");
       
    31 
       
    32 			if (t.rpcUrl == '{backend}') {
       
    33 				// Sniff if the browser supports native spellchecking (Don't know of a better way)
       
    34 				if (tinymce.isIE)
       
    35 					return;
       
    36 
       
    37 				t.hasSupport = true;
       
    38 
       
    39 				// Disable the context menu when spellchecking is active
       
    40 				ed.onContextMenu.addToTop(function(ed, e) {
       
    41 					if (t.active)
       
    42 						return false;
       
    43 				});
       
    44 			}
       
    45 
       
    46 			// Register commands
       
    47 			ed.addCommand('mceSpellCheck', function() {
       
    48 				if (t.rpcUrl == '{backend}') {
       
    49 					// Enable/disable native spellchecker
       
    50 					t.editor.getBody().spellcheck = t.active = !t.active;
       
    51 					return;
       
    52 				}
       
    53 
       
    54 				if (!t.active) {
       
    55 					ed.setProgressState(1);
       
    56 					t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) {
       
    57 						if (r.length > 0) {
       
    58 							t.active = 1;
       
    59 							t._markWords(r);
       
    60 							ed.setProgressState(0);
       
    61 							ed.nodeChanged();
       
    62 						} else {
       
    63 							ed.setProgressState(0);
       
    64 
       
    65 							if (ed.getParam('spellchecker_report_no_misspellings', true))
       
    66 								ed.windowManager.alert('spellchecker.no_mpell');
       
    67 						}
       
    68 					});
       
    69 				} else
       
    70 					t._done();
       
    71 			});
       
    72 
       
    73 			if (ed.settings.content_css !== false)
       
    74 				ed.contentCSS.push(url + '/css/content.css');
       
    75 
       
    76 			ed.onClick.add(t._showMenu, t);
       
    77 			ed.onContextMenu.add(t._showMenu, t);
       
    78 			ed.onBeforeGetContent.add(function() {
       
    79 				if (t.active)
       
    80 					t._removeWords();
       
    81 			});
       
    82 
       
    83 			ed.onNodeChange.add(function(ed, cm) {
       
    84 				cm.setActive('spellchecker', t.active);
       
    85 			});
       
    86 
       
    87 			ed.onSetContent.add(function() {
       
    88 				t._done();
       
    89 			});
       
    90 
       
    91 			ed.onBeforeGetContent.add(function() {
       
    92 				t._done();
       
    93 			});
       
    94 
       
    95 			ed.onBeforeExecCommand.add(function(ed, cmd) {
       
    96 				if (cmd == 'mceFullScreen')
       
    97 					t._done();
       
    98 			});
       
    99 
       
   100 			// Find selected language
       
   101 			t.languages = {};
       
   102 			each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) {
       
   103 				if (k.indexOf('+') === 0) {
       
   104 					k = k.substring(1);
       
   105 					t.selectedLang = v;
       
   106 				}
       
   107 
       
   108 				t.languages[k] = v;
       
   109 			});
       
   110 		},
       
   111 
       
   112 		createControl : function(n, cm) {
       
   113 			var t = this, c, ed = t.editor;
       
   114 
       
   115 			if (n == 'spellchecker') {
       
   116 				// Use basic button if we use the native spellchecker
       
   117 				if (t.rpcUrl == '{backend}') {
       
   118 					// Create simple toggle button if we have native support
       
   119 					if (t.hasSupport)
       
   120 						c = cm.createButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t});
       
   121 
       
   122 					return c;
       
   123 				}
       
   124 
       
   125 				c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t});
       
   126 
       
   127 				c.onRenderMenu.add(function(c, m) {
       
   128 					m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   129 					each(t.languages, function(v, k) {
       
   130 						var o = {icon : 1}, mi;
       
   131 
       
   132 						o.onclick = function() {
       
   133 							if (v == t.selectedLang) {
       
   134 								return;
       
   135 							}
       
   136 							mi.setSelected(1);
       
   137 							t.selectedItem.setSelected(0);
       
   138 							t.selectedItem = mi;
       
   139 							t.selectedLang = v;
       
   140 						};
       
   141 
       
   142 						o.title = k;
       
   143 						mi = m.add(o);
       
   144 						mi.setSelected(v == t.selectedLang);
       
   145 
       
   146 						if (v == t.selectedLang)
       
   147 							t.selectedItem = mi;
       
   148 					})
       
   149 				});
       
   150 
       
   151 				return c;
       
   152 			}
       
   153 		},
       
   154 
       
   155 		// Internal functions
       
   156 
       
   157 		_walk : function(n, f) {
       
   158 			var d = this.editor.getDoc(), w;
       
   159 
       
   160 			if (d.createTreeWalker) {
       
   161 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
       
   162 
       
   163 				while ((n = w.nextNode()) != null)
       
   164 					f.call(this, n);
       
   165 			} else
       
   166 				tinymce.walk(n, f, 'childNodes');
       
   167 		},
       
   168 
       
   169 		_getSeparators : function() {
       
   170 			var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');
       
   171 
       
   172 			// Build word separator regexp
       
   173 			for (i=0; i<str.length; i++)
       
   174 				re += '\\' + str.charAt(i);
       
   175 
       
   176 			return re;
       
   177 		},
       
   178 
       
   179 		_getWords : function() {
       
   180 			var ed = this.editor, wl = [], tx = '', lo = {}, rawWords = [];
       
   181 
       
   182 			// Get area text
       
   183 			this._walk(ed.getBody(), function(n) {
       
   184 				if (n.nodeType == 3)
       
   185 					tx += n.nodeValue + ' ';
       
   186 			});
       
   187 
       
   188 			// split the text up into individual words
       
   189 			if (ed.getParam('spellchecker_word_pattern')) {
       
   190 				// look for words that match the pattern
       
   191 				rawWords = tx.match('(' + ed.getParam('spellchecker_word_pattern') + ')', 'gi');
       
   192 			} else {
       
   193 				// Split words by separator
       
   194 				tx = tx.replace(new RegExp('([0-9]|[' + this._getSeparators() + '])', 'g'), ' ');
       
   195 				tx = tinymce.trim(tx.replace(/(\s+)/g, ' '));
       
   196 				rawWords = tx.split(' ');
       
   197 			}
       
   198 
       
   199 			// Build word array and remove duplicates
       
   200 			each(rawWords, function(v) {
       
   201 				if (!lo[v]) {
       
   202 					wl.push(v);
       
   203 					lo[v] = 1;
       
   204 				}
       
   205 			});
       
   206 
       
   207 			return wl;
       
   208 		},
       
   209 
       
   210 		_removeWords : function(w) {
       
   211 			var ed = this.editor, dom = ed.dom, se = ed.selection, b = se.getBookmark();
       
   212 
       
   213 			each(dom.select('span').reverse(), function(n) {
       
   214 				if (n && (dom.hasClass(n, 'mceItemHiddenSpellWord') || dom.hasClass(n, 'mceItemHidden'))) {
       
   215 					if (!w || dom.decode(n.innerHTML) == w)
       
   216 						dom.remove(n, 1);
       
   217 				}
       
   218 			});
       
   219 
       
   220 			se.moveToBookmark(b);
       
   221 		},
       
   222 
       
   223 		_markWords : function(wl) {
       
   224 			var ed = this.editor, dom = ed.dom, doc = ed.getDoc(), se = ed.selection, b = se.getBookmark(), nl = [],
       
   225 				w = wl.join('|'), re = this._getSeparators(), rx = new RegExp('(^|[' + re + '])(' + w + ')(?=[' + re + ']|$)', 'g');
       
   226 
       
   227 			// Collect all text nodes
       
   228 			this._walk(ed.getBody(), function(n) {
       
   229 				if (n.nodeType == 3) {
       
   230 					nl.push(n);
       
   231 				}
       
   232 			});
       
   233 
       
   234 			// Wrap incorrect words in spans
       
   235 			each(nl, function(n) {
       
   236 				var node, elem, txt, pos, v = n.nodeValue;
       
   237 
       
   238 				if (rx.test(v)) {
       
   239 					// Encode the content
       
   240 					v = dom.encode(v);
       
   241 					// Create container element
       
   242 					elem = dom.create('span', {'class' : 'mceItemHidden'});
       
   243 
       
   244 					// Following code fixes IE issues by creating text nodes
       
   245 					// using DOM methods instead of innerHTML.
       
   246 					// Bug #3124: <PRE> elements content is broken after spellchecking.
       
   247 					// Bug #1408: Preceding whitespace characters are removed
       
   248 					// @TODO: I'm not sure that both are still issues on IE9.
       
   249 					if (tinymce.isIE) {
       
   250 						// Enclose mispelled words with temporal tag
       
   251 						v = v.replace(rx, '$1<mcespell>$2</mcespell>');
       
   252 						// Loop over the content finding mispelled words
       
   253 						while ((pos = v.indexOf('<mcespell>')) != -1) {
       
   254 							// Add text node for the content before the word
       
   255 							txt = v.substring(0, pos);
       
   256 							if (txt.length) {
       
   257 								node = doc.createTextNode(dom.decode(txt));
       
   258 								elem.appendChild(node);
       
   259 							}
       
   260 							v = v.substring(pos+10);
       
   261 							pos = v.indexOf('</mcespell>');
       
   262 							txt = v.substring(0, pos);
       
   263 							v = v.substring(pos+11);
       
   264 							// Add span element for the word
       
   265 							elem.appendChild(dom.create('span', {'class' : 'mceItemHiddenSpellWord'}, txt));
       
   266 						}
       
   267 						// Add text node for the rest of the content
       
   268 						if (v.length) {
       
   269 							node = doc.createTextNode(dom.decode(v));
       
   270 							elem.appendChild(node);
       
   271 						}
       
   272 					} else {
       
   273 						// Other browsers preserve whitespace characters on innerHTML usage
       
   274 						elem.innerHTML = v.replace(rx, '$1<span class="mceItemHiddenSpellWord">$2</span>');
       
   275 					}
       
   276 
       
   277 					// Finally, replace the node with the container
       
   278 					dom.replace(elem, n);
       
   279 				}
       
   280 			});
       
   281 
       
   282 			se.moveToBookmark(b);
       
   283 		},
       
   284 
       
   285 		_showMenu : function(ed, e) {
       
   286 			var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()), wordSpan = e.target;
       
   287 
       
   288 			e = 0; // Fixes IE memory leak
       
   289 
       
   290 			if (!m) {
       
   291 				m = ed.controlManager.createDropMenu('spellcheckermenu', {'class' : 'mceNoIcons'});
       
   292 				t._menu = m;
       
   293 			}
       
   294 
       
   295 			if (dom.hasClass(wordSpan, 'mceItemHiddenSpellWord')) {
       
   296 				m.removeAll();
       
   297 				m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   298 
       
   299 				t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(wordSpan.innerHTML)], function(r) {
       
   300 					var ignoreRpc;
       
   301 
       
   302 					m.removeAll();
       
   303 
       
   304 					if (r.length > 0) {
       
   305 						m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   306 						each(r, function(v) {
       
   307 							m.add({title : v, onclick : function() {
       
   308 								dom.replace(ed.getDoc().createTextNode(v), wordSpan);
       
   309 								t._checkDone();
       
   310 							}});
       
   311 						});
       
   312 
       
   313 						m.addSeparator();
       
   314 					} else
       
   315 						m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   316 
       
   317 					if (ed.getParam('show_ignore_words', true)) {
       
   318 						ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", '');
       
   319 						m.add({
       
   320 							title : 'spellchecker.ignore_word',
       
   321 							onclick : function() {
       
   322 								var word = wordSpan.innerHTML;
       
   323 
       
   324 								dom.remove(wordSpan, 1);
       
   325 								t._checkDone();
       
   326 
       
   327 								// tell the server if we need to
       
   328 								if (ignoreRpc) {
       
   329 									ed.setProgressState(1);
       
   330 									t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) {
       
   331 										ed.setProgressState(0);
       
   332 									});
       
   333 								}
       
   334 							}
       
   335 						});
       
   336 
       
   337 						m.add({
       
   338 							title : 'spellchecker.ignore_words',
       
   339 							onclick : function() {
       
   340 								var word = wordSpan.innerHTML;
       
   341 
       
   342 								t._removeWords(dom.decode(word));
       
   343 								t._checkDone();
       
   344 
       
   345 								// tell the server if we need to
       
   346 								if (ignoreRpc) {
       
   347 									ed.setProgressState(1);
       
   348 									t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) {
       
   349 										ed.setProgressState(0);
       
   350 									});
       
   351 								}
       
   352 							}
       
   353 						});
       
   354 					}
       
   355 
       
   356 					if (t.editor.getParam("spellchecker_enable_learn_rpc")) {
       
   357 						m.add({
       
   358 							title : 'spellchecker.learn_word',
       
   359 							onclick : function() {
       
   360 								var word = wordSpan.innerHTML;
       
   361 
       
   362 								dom.remove(wordSpan, 1);
       
   363 								t._checkDone();
       
   364 
       
   365 								ed.setProgressState(1);
       
   366 								t._sendRPC('learnWord', [t.selectedLang, word], function(r) {
       
   367 									ed.setProgressState(0);
       
   368 								});
       
   369 							}
       
   370 						});
       
   371 					}
       
   372 
       
   373 					m.update();
       
   374 				});
       
   375 
       
   376 				p1 = DOM.getPos(ed.getContentAreaContainer());
       
   377 				m.settings.offset_x = p1.x;
       
   378 				m.settings.offset_y = p1.y;
       
   379 
       
   380 				ed.selection.select(wordSpan);
       
   381 				p1 = dom.getPos(wordSpan);
       
   382 				m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y);
       
   383 
       
   384 				return tinymce.dom.Event.cancel(e);
       
   385 			} else
       
   386 				m.hideMenu();
       
   387 		},
       
   388 
       
   389 		_checkDone : function() {
       
   390 			var t = this, ed = t.editor, dom = ed.dom, o;
       
   391 
       
   392 			each(dom.select('span'), function(n) {
       
   393 				if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) {
       
   394 					o = true;
       
   395 					return false;
       
   396 				}
       
   397 			});
       
   398 
       
   399 			if (!o)
       
   400 				t._done();
       
   401 		},
       
   402 
       
   403 		_done : function() {
       
   404 			var t = this, la = t.active;
       
   405 
       
   406 			if (t.active) {
       
   407 				t.active = 0;
       
   408 				t._removeWords();
       
   409 
       
   410 				if (t._menu)
       
   411 					t._menu.hideMenu();
       
   412 
       
   413 				if (la)
       
   414 					t.editor.nodeChanged();
       
   415 			}
       
   416 		},
       
   417 
       
   418 		_sendRPC : function(m, p, cb) {
       
   419 			var t = this;
       
   420 
       
   421 			JSONRequest.sendRPC({
       
   422 				url : t.rpcUrl,
       
   423 				method : m,
       
   424 				params : p,
       
   425 				success : cb,
       
   426 				error : function(e, x) {
       
   427 					t.editor.setProgressState(0);
       
   428 					t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText));
       
   429 				}
       
   430 			});
       
   431 		}
       
   432 	});
       
   433 
       
   434 	// Register plugin
       
   435 	tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin);
       
   436 })();