wp/wp-includes/js/tinymce/plugins/spellchecker/editor_plugin_src.js
changeset 0 d970ebf37754
equal deleted inserted replaced
-1:000000000000 0:d970ebf37754
       
     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 					t.menuItems = {};
       
   130 					each(t.languages, function(v, k) {
       
   131 						var o = {icon : 1}, mi;
       
   132 
       
   133 						o.onclick = function() {
       
   134 							if (v == t.selectedLang) {
       
   135 								return;
       
   136 							}
       
   137 							t._updateMenu(mi);
       
   138 							t.selectedLang = v;
       
   139 						};
       
   140 
       
   141 						o.title = k;
       
   142 						mi = m.add(o);
       
   143 						mi.setSelected(v == t.selectedLang);
       
   144 						t.menuItems[v] = mi;
       
   145 						if (v == t.selectedLang)
       
   146 							t.selectedItem = mi;
       
   147 					});
       
   148 				});
       
   149 
       
   150 
       
   151 
       
   152 				return c;
       
   153 			}
       
   154 		},
       
   155 
       
   156 		setLanguage: function(lang) {
       
   157 			var t = this;
       
   158 
       
   159 			if (lang == t.selectedLang) {
       
   160 				// allowed
       
   161 				return;
       
   162 			}
       
   163 
       
   164 			if (tinymce.grep(t.languages, function(v) { return v === lang; }).length === 0) {
       
   165 				throw "Unknown language: " + lang;
       
   166 			}
       
   167 
       
   168 			t.selectedLang = lang;
       
   169 
       
   170 			// if the menu has been shown, update it as well
       
   171 			if (t.menuItems) {
       
   172 				t._updateMenu(t.menuItems[lang]);
       
   173 			}
       
   174 
       
   175 			if (t.active) {
       
   176 				// clear error in the old language.
       
   177 				t._done();
       
   178 
       
   179 				// Don't immediately block the UI to check spelling in the new language, this is an API not a user action.
       
   180 			}
       
   181 		},
       
   182 
       
   183 		// Internal functions
       
   184 
       
   185 		_updateMenu: function(mi) {
       
   186 			mi.setSelected(1);
       
   187 			this.selectedItem.setSelected(0);
       
   188 			this.selectedItem = mi;
       
   189 		},
       
   190 
       
   191 		_walk : function(n, f) {
       
   192 			var d = this.editor.getDoc(), w;
       
   193 
       
   194 			if (d.createTreeWalker) {
       
   195 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
       
   196 
       
   197 				while ((n = w.nextNode()) != null)
       
   198 					f.call(this, n);
       
   199 			} else
       
   200 				tinymce.walk(n, f, 'childNodes');
       
   201 		},
       
   202 
       
   203 		_getSeparators : function() {
       
   204 			var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');
       
   205 
       
   206 			// Build word separator regexp
       
   207 			for (i=0; i<str.length; i++)
       
   208 				re += '\\' + str.charAt(i);
       
   209 
       
   210 			return re;
       
   211 		},
       
   212 
       
   213 		_getWords : function() {
       
   214 			var ed = this.editor, wl = [], tx = '', lo = {}, rawWords = [];
       
   215 
       
   216 			// Get area text
       
   217 			this._walk(ed.getBody(), function(n) {
       
   218 				if (n.nodeType == 3)
       
   219 					tx += n.nodeValue + ' ';
       
   220 			});
       
   221 
       
   222 			// split the text up into individual words
       
   223 			if (ed.getParam('spellchecker_word_pattern')) {
       
   224 				// look for words that match the pattern
       
   225 				rawWords = tx.match('(' + ed.getParam('spellchecker_word_pattern') + ')', 'gi');
       
   226 			} else {
       
   227 				// Split words by separator
       
   228 				tx = tx.replace(new RegExp('([0-9]|[' + this._getSeparators() + '])', 'g'), ' ');
       
   229 				tx = tinymce.trim(tx.replace(/(\s+)/g, ' '));
       
   230 				rawWords = tx.split(' ');
       
   231 			}
       
   232 
       
   233 			// Build word array and remove duplicates
       
   234 			each(rawWords, function(v) {
       
   235 				if (!lo[v]) {
       
   236 					wl.push(v);
       
   237 					lo[v] = 1;
       
   238 				}
       
   239 			});
       
   240 
       
   241 			return wl;
       
   242 		},
       
   243 
       
   244 		_removeWords : function(w) {
       
   245 			var ed = this.editor, dom = ed.dom, se = ed.selection, r = se.getRng(true);
       
   246 
       
   247 			each(dom.select('span').reverse(), function(n) {
       
   248 				if (n && (dom.hasClass(n, 'mceItemHiddenSpellWord') || dom.hasClass(n, 'mceItemHidden'))) {
       
   249 					if (!w || dom.decode(n.innerHTML) == w)
       
   250 						dom.remove(n, 1);
       
   251 				}
       
   252 			});
       
   253 
       
   254 			se.setRng(r);
       
   255 		},
       
   256 
       
   257 		_markWords : function(wl) {
       
   258 			var ed = this.editor, dom = ed.dom, doc = ed.getDoc(), se = ed.selection, r = se.getRng(true), nl = [],
       
   259 				w = wl.join('|'), re = this._getSeparators(), rx = new RegExp('(^|[' + re + '])(' + w + ')(?=[' + re + ']|$)', 'g');
       
   260 
       
   261 			// Collect all text nodes
       
   262 			this._walk(ed.getBody(), function(n) {
       
   263 				if (n.nodeType == 3) {
       
   264 					nl.push(n);
       
   265 				}
       
   266 			});
       
   267 
       
   268 			// Wrap incorrect words in spans
       
   269 			each(nl, function(n) {
       
   270 				var node, elem, txt, pos, v = n.nodeValue;
       
   271 
       
   272 				rx.lastIndex = 0;
       
   273 				if (rx.test(v)) {
       
   274 					// Encode the content
       
   275 					v = dom.encode(v);
       
   276 					// Create container element
       
   277 					elem = dom.create('span', {'class' : 'mceItemHidden'});
       
   278 
       
   279 					// Following code fixes IE issues by creating text nodes
       
   280 					// using DOM methods instead of innerHTML.
       
   281 					// Bug #3124: <PRE> elements content is broken after spellchecking.
       
   282 					// Bug #1408: Preceding whitespace characters are removed
       
   283 					// @TODO: I'm not sure that both are still issues on IE9.
       
   284 					if (tinymce.isIE) {
       
   285 						// Enclose mispelled words with temporal tag
       
   286 						v = v.replace(rx, '$1<mcespell>$2</mcespell>');
       
   287 						// Loop over the content finding mispelled words
       
   288 						while ((pos = v.indexOf('<mcespell>')) != -1) {
       
   289 							// Add text node for the content before the word
       
   290 							txt = v.substring(0, pos);
       
   291 							if (txt.length) {
       
   292 								node = doc.createTextNode(dom.decode(txt));
       
   293 								elem.appendChild(node);
       
   294 							}
       
   295 							v = v.substring(pos+10);
       
   296 							pos = v.indexOf('</mcespell>');
       
   297 							txt = v.substring(0, pos);
       
   298 							v = v.substring(pos+11);
       
   299 							// Add span element for the word
       
   300 							elem.appendChild(dom.create('span', {'class' : 'mceItemHiddenSpellWord'}, txt));
       
   301 						}
       
   302 						// Add text node for the rest of the content
       
   303 						if (v.length) {
       
   304 							node = doc.createTextNode(dom.decode(v));
       
   305 							elem.appendChild(node);
       
   306 						}
       
   307 					} else {
       
   308 						// Other browsers preserve whitespace characters on innerHTML usage
       
   309 						elem.innerHTML = v.replace(rx, '$1<span class="mceItemHiddenSpellWord">$2</span>');
       
   310 					}
       
   311 
       
   312 					// Finally, replace the node with the container
       
   313 					dom.replace(elem, n);
       
   314 				}
       
   315 			});
       
   316 
       
   317 			se.setRng(r);
       
   318 		},
       
   319 
       
   320 		_showMenu : function(ed, e) {
       
   321 			var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()), wordSpan = e.target;
       
   322 
       
   323 			e = 0; // Fixes IE memory leak
       
   324 
       
   325 			if (!m) {
       
   326 				m = ed.controlManager.createDropMenu('spellcheckermenu', {'class' : 'mceNoIcons'});
       
   327 				t._menu = m;
       
   328 			}
       
   329 
       
   330 			if (dom.hasClass(wordSpan, 'mceItemHiddenSpellWord')) {
       
   331 				m.removeAll();
       
   332 				m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   333 
       
   334 				t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(wordSpan.innerHTML)], function(r) {
       
   335 					var ignoreRpc;
       
   336 
       
   337 					m.removeAll();
       
   338 
       
   339 					if (r.length > 0) {
       
   340 						m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   341 						each(r, function(v) {
       
   342 							m.add({title : v, onclick : function() {
       
   343 								dom.replace(ed.getDoc().createTextNode(v), wordSpan);
       
   344 								t._checkDone();
       
   345 							}});
       
   346 						});
       
   347 
       
   348 						m.addSeparator();
       
   349 					} else
       
   350 						m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   351 
       
   352 					if (ed.getParam('show_ignore_words', true)) {
       
   353 						ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", '');
       
   354 						m.add({
       
   355 							title : 'spellchecker.ignore_word',
       
   356 							onclick : function() {
       
   357 								var word = wordSpan.innerHTML;
       
   358 
       
   359 								dom.remove(wordSpan, 1);
       
   360 								t._checkDone();
       
   361 
       
   362 								// tell the server if we need to
       
   363 								if (ignoreRpc) {
       
   364 									ed.setProgressState(1);
       
   365 									t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) {
       
   366 										ed.setProgressState(0);
       
   367 									});
       
   368 								}
       
   369 							}
       
   370 						});
       
   371 
       
   372 						m.add({
       
   373 							title : 'spellchecker.ignore_words',
       
   374 							onclick : function() {
       
   375 								var word = wordSpan.innerHTML;
       
   376 
       
   377 								t._removeWords(dom.decode(word));
       
   378 								t._checkDone();
       
   379 
       
   380 								// tell the server if we need to
       
   381 								if (ignoreRpc) {
       
   382 									ed.setProgressState(1);
       
   383 									t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) {
       
   384 										ed.setProgressState(0);
       
   385 									});
       
   386 								}
       
   387 							}
       
   388 						});
       
   389 					}
       
   390 
       
   391 					if (t.editor.getParam("spellchecker_enable_learn_rpc")) {
       
   392 						m.add({
       
   393 							title : 'spellchecker.learn_word',
       
   394 							onclick : function() {
       
   395 								var word = wordSpan.innerHTML;
       
   396 
       
   397 								dom.remove(wordSpan, 1);
       
   398 								t._checkDone();
       
   399 
       
   400 								ed.setProgressState(1);
       
   401 								t._sendRPC('learnWord', [t.selectedLang, word], function(r) {
       
   402 									ed.setProgressState(0);
       
   403 								});
       
   404 							}
       
   405 						});
       
   406 					}
       
   407 
       
   408 					m.update();
       
   409 				});
       
   410 
       
   411 				p1 = DOM.getPos(ed.getContentAreaContainer());
       
   412 				m.settings.offset_x = p1.x;
       
   413 				m.settings.offset_y = p1.y;
       
   414 
       
   415 				ed.selection.select(wordSpan);
       
   416 				p1 = dom.getPos(wordSpan);
       
   417 				m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y);
       
   418 
       
   419 				return tinymce.dom.Event.cancel(e);
       
   420 			} else
       
   421 				m.hideMenu();
       
   422 		},
       
   423 
       
   424 		_checkDone : function() {
       
   425 			var t = this, ed = t.editor, dom = ed.dom, o;
       
   426 
       
   427 			each(dom.select('span'), function(n) {
       
   428 				if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) {
       
   429 					o = true;
       
   430 					return false;
       
   431 				}
       
   432 			});
       
   433 
       
   434 			if (!o)
       
   435 				t._done();
       
   436 		},
       
   437 
       
   438 		_done : function() {
       
   439 			var t = this, la = t.active;
       
   440 
       
   441 			if (t.active) {
       
   442 				t.active = 0;
       
   443 				t._removeWords();
       
   444 
       
   445 				if (t._menu)
       
   446 					t._menu.hideMenu();
       
   447 
       
   448 				if (la)
       
   449 					t.editor.nodeChanged();
       
   450 			}
       
   451 		},
       
   452 
       
   453 		_sendRPC : function(m, p, cb) {
       
   454 			var t = this;
       
   455 
       
   456 			JSONRequest.sendRPC({
       
   457 				url : t.rpcUrl,
       
   458 				method : m,
       
   459 				params : p,
       
   460 				success : cb,
       
   461 				error : function(e, x) {
       
   462 					t.editor.setProgressState(0);
       
   463 					t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText));
       
   464 				}
       
   465 			});
       
   466 		}
       
   467 	});
       
   468 
       
   469 	// Register plugin
       
   470 	tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin);
       
   471 })();