includes/clientside/tinymce/plugins/spellchecker/editor_plugin_src.js
changeset 476 f26a69c40431
child 543 dffcbfbc4e59
equal deleted inserted replaced
475:51386f1852b8 476:f26a69c40431
       
     1 /**
       
     2  * $Id: editor_plugin_src.js 425 2007-11-21 15:17:39Z spocke $
       
     3  *
       
     4  * @author Moxiecode
       
     5  * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.
       
     6  */
       
     7 
       
     8 (function() {
       
     9 	var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM;
       
    10 
       
    11 	tinymce.create('tinymce.plugins.SpellcheckerPlugin', {
       
    12 		getInfo : function() {
       
    13 			return {
       
    14 				longname : 'Spellchecker',
       
    15 				author : 'Moxiecode Systems AB',
       
    16 				authorurl : 'http://tinymce.moxiecode.com',
       
    17 				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker',
       
    18 				version : tinymce.majorVersion + "." + tinymce.minorVersion
       
    19 			};
       
    20 		},
       
    21 
       
    22 		init : function(ed, url) {
       
    23 			var t = this, cm;
       
    24 
       
    25 			t.url = url;
       
    26 			t.editor = ed;
       
    27 
       
    28 			// Register commands
       
    29 			ed.addCommand('mceSpellCheck', function() {
       
    30 				if (!t.active) {
       
    31 					ed.setProgressState(1);
       
    32 					t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) {
       
    33 						if (r.length > 0) {
       
    34 							t.active = 1;
       
    35 							t._markWords(r);
       
    36 							ed.setProgressState(0);
       
    37 							ed.nodeChanged();
       
    38 						} else {
       
    39 							ed.setProgressState(0);
       
    40 							ed.windowManager.alert('spellchecker.no_mpell');
       
    41 						}
       
    42 					});
       
    43 				} else
       
    44 					t._done();
       
    45 			});
       
    46 
       
    47 			ed.onInit.add(function() {
       
    48 				ed.dom.loadCSS(url + '/css/content.css');
       
    49 			});
       
    50 
       
    51 			ed.onClick.add(t._showMenu, t);
       
    52 			ed.onContextMenu.add(t._showMenu, t);
       
    53 			ed.onBeforeGetContent.add(function() {
       
    54 				if (t.active)
       
    55 					t._removeWords();
       
    56 			});
       
    57 
       
    58 			ed.onNodeChange.add(function(ed, cm) {
       
    59 				cm.setActive('spellchecker', t.active);
       
    60 			});
       
    61 
       
    62 			ed.onSetContent.add(function() {
       
    63 				t._done();
       
    64 			});
       
    65 
       
    66 			ed.onBeforeGetContent.add(function() {
       
    67 				t._done();
       
    68 			});
       
    69 
       
    70 			ed.onBeforeExecCommand.add(function(ed, cmd) {
       
    71 				if (cmd == 'mceFullScreen')
       
    72 					t._done();
       
    73 			});
       
    74 
       
    75 			// Find selected language
       
    76 			t.languages = {};
       
    77 			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) {
       
    78 				if (k.indexOf('+') === 0) {
       
    79 					k = k.substring(1);
       
    80 					t.selectedLang = v;
       
    81 				}
       
    82 
       
    83 				t.languages[k] = v;
       
    84 			});
       
    85 		},
       
    86 
       
    87 		createControl : function(n, cm) {
       
    88 			var t = this, c, ed = t.editor;
       
    89 
       
    90 			if (n == 'spellchecker') {
       
    91 				c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t});
       
    92 
       
    93 				c.onRenderMenu.add(function(c, m) {
       
    94 					m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
    95 					each(t.languages, function(v, k) {
       
    96 						var o = {icon : 1}, mi;
       
    97 
       
    98 						o.onclick = function() {
       
    99 							mi.setSelected(1);
       
   100 							t.selectedItem.setSelected(0);
       
   101 							t.selectedItem = mi;
       
   102 							t.selectedLang = v;
       
   103 						};
       
   104 
       
   105 						o.title = k;
       
   106 						mi = m.add(o);
       
   107 						mi.setSelected(v == t.selectedLang);
       
   108 
       
   109 						if (v == t.selectedLang)
       
   110 							t.selectedItem = mi;
       
   111 					})
       
   112 				});
       
   113 
       
   114 				return c;
       
   115 			}
       
   116 		},
       
   117 
       
   118 		// Internal functions
       
   119 
       
   120 		_walk : function(n, f) {
       
   121 			var d = this.editor.getDoc(), w;
       
   122 
       
   123 			if (d.createTreeWalker) {
       
   124 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
       
   125 
       
   126 				while ((n = w.nextNode()) != null)
       
   127 					f.call(this, n);
       
   128 			} else
       
   129 				tinymce.walk(n, f, 'childNodes');
       
   130 		},
       
   131 
       
   132 		_getSeparators : function() {
       
   133 			var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');
       
   134 
       
   135 			// Build word separator regexp
       
   136 			for (i=0; i<str.length; i++)
       
   137 				re += '\\' + str.charAt(i);
       
   138 
       
   139 			return re;
       
   140 		},
       
   141 
       
   142 		_getWords : function() {
       
   143 			var ed = this.editor, wl = [], tx = '', lo = {};
       
   144 
       
   145 			// Get area text
       
   146 			this._walk(ed.getBody(), function(n) {
       
   147 				if (n.nodeType == 3)
       
   148 					tx += n.nodeValue + ' ';
       
   149 			});
       
   150 
       
   151 			// Split words by separator
       
   152 			tx = tx.replace(new RegExp('([0-9]|[' + this._getSeparators() + '])', 'g'), ' ');
       
   153 			tx = tinymce.trim(tx.replace(/(\s+)/g, ' '));
       
   154 
       
   155 			// Build word array and remove duplicates
       
   156 			each(tx.split(' '), function(v) {
       
   157 				if (!lo[v]) {
       
   158 					wl.push(v);
       
   159 					lo[v] = 1;
       
   160 				}
       
   161 			});
       
   162 
       
   163 			return wl;
       
   164 		},
       
   165 
       
   166 		_removeWords : function(w) {
       
   167 			var ed = this.editor, dom = ed.dom, se = ed.selection, b = se.getBookmark();
       
   168 
       
   169 			each(dom.select('span').reverse(), function(n) {
       
   170 				if (n && (dom.hasClass(n, 'mceItemHiddenSpellWord') || dom.hasClass(n, 'mceItemHidden'))) {
       
   171 					if (!w || dom.decode(n.innerHTML) == w)
       
   172 						dom.remove(n, 1);
       
   173 				}
       
   174 			});
       
   175 
       
   176 			se.moveToBookmark(b);
       
   177 		},
       
   178 
       
   179 		_markWords : function(wl) {
       
   180 			var r1, r2, r3, r4, r5, w = '', ed = this.editor, re = this._getSeparators(), dom = ed.dom, nl = [];
       
   181 			var se = ed.selection, b = se.getBookmark();
       
   182 
       
   183 			each(wl, function(v) {
       
   184 				w += (w ? '|' : '') + v;
       
   185 			});
       
   186 
       
   187 			r1 = new RegExp('([' + re + '])(' + w + ')([' + re + '])', 'g');
       
   188 			r2 = new RegExp('^(' + w + ')', 'g');
       
   189 			r3 = new RegExp('(' + w + ')([' + re + ']?)$', 'g');
       
   190 			r4 = new RegExp('^(' + w + ')([' + re + ']?)$', 'g');
       
   191 			r5 = new RegExp('(' + w + ')([' + re + '])', 'g');
       
   192 
       
   193 			// Collect all text nodes
       
   194 			this._walk(this.editor.getBody(), function(n) {
       
   195 				if (n.nodeType == 3) {
       
   196 					nl.push(n);
       
   197 				}
       
   198 			});
       
   199 
       
   200 			// Wrap incorrect words in spans
       
   201 			each(nl, function(n) {
       
   202 				var v;
       
   203 
       
   204 				if (n.nodeType == 3) {
       
   205 					v = n.nodeValue;
       
   206 
       
   207 					if (r1.test(v) || r2.test(v) || r3.test(v) || r4.test(v)) {
       
   208 						v = dom.encode(v);
       
   209 						v = v.replace(r5, '<span class="mceItemHiddenSpellWord">$1</span>$2');
       
   210 						v = v.replace(r3, '<span class="mceItemHiddenSpellWord">$1</span>$2');
       
   211 
       
   212 						dom.replace(dom.create('span', {'class' : 'mceItemHidden'}, v), n);
       
   213 					}
       
   214 				}
       
   215 			});
       
   216 
       
   217 			se.moveToBookmark(b);
       
   218 		},
       
   219 
       
   220 		_showMenu : function(ed, e) {
       
   221 			var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin());
       
   222 
       
   223 			if (!m) {
       
   224 				p1 = DOM.getPos(ed.getContentAreaContainer());
       
   225 				//p2 = DOM.getPos(ed.getContainer());
       
   226 
       
   227 				m = ed.controlManager.createDropMenu('spellcheckermenu', {
       
   228 					offset_x : p1.x,
       
   229 					offset_y : p1.y,
       
   230 					'class' : 'noIcons'
       
   231 				});
       
   232 
       
   233 				t._menu = m;
       
   234 			}
       
   235 
       
   236 			if (dom.hasClass(e.target, 'mceItemHiddenSpellWord')) {
       
   237 				m.removeAll();
       
   238 				m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   239 
       
   240 				t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(e.target.innerHTML)], function(r) {
       
   241 					m.removeAll();
       
   242 
       
   243 					if (r.length > 0) {
       
   244 						m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   245 						each(r, function(v) {
       
   246 							m.add({title : v, onclick : function() {
       
   247 								dom.replace(ed.getDoc().createTextNode(v), e.target);
       
   248 								t._checkDone();
       
   249 							}});
       
   250 						});
       
   251 
       
   252 						m.addSeparator();
       
   253 					} else
       
   254 						m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
       
   255 
       
   256 					m.add({
       
   257 						title : 'spellchecker.ignore_word',
       
   258 						onclick : function() {
       
   259 							dom.remove(e.target, 1);
       
   260 							t._checkDone();
       
   261 						}
       
   262 					});
       
   263 
       
   264 					m.add({
       
   265 						title : 'spellchecker.ignore_words',
       
   266 						onclick : function() {
       
   267 							t._removeWords(dom.decode(e.target.innerHTML));
       
   268 							t._checkDone();
       
   269 						}
       
   270 					});
       
   271 
       
   272 					m.update();
       
   273 				});
       
   274 
       
   275 				ed.selection.select(e.target);
       
   276 				p1 = dom.getPos(e.target);
       
   277 				m.showMenu(p1.x, p1.y + e.target.offsetHeight - vp.y);
       
   278 
       
   279 				return tinymce.dom.Event.cancel(e);
       
   280 			} else
       
   281 				m.hideMenu();
       
   282 		},
       
   283 
       
   284 		_checkDone : function() {
       
   285 			var t = this, ed = t.editor, dom = ed.dom, o;
       
   286 
       
   287 			each(dom.select('span'), function(n) {
       
   288 				if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) {
       
   289 					o = true;
       
   290 					return false;
       
   291 				}
       
   292 			});
       
   293 
       
   294 			if (!o)
       
   295 				t._done();
       
   296 		},
       
   297 
       
   298 		_done : function() {
       
   299 			var t = this, la = t.active;
       
   300 
       
   301 			t.active = 0;
       
   302 			t._removeWords();
       
   303 
       
   304 			if (t._menu)
       
   305 				t._menu.hideMenu();
       
   306 
       
   307 			if (la)
       
   308 				t.editor.nodeChanged();
       
   309 		},
       
   310 
       
   311 		_sendRPC : function(m, p, cb) {
       
   312 			var t = this, url = t.editor.getParam("spellchecker_rpc_url", "{backend}");
       
   313 
       
   314 			if (url == '{backend}') {
       
   315 				t.editor.setProgressState(0);
       
   316 				alert('Please specify: spellchecker_rpc_url');
       
   317 				return;
       
   318 			}
       
   319 
       
   320 			JSONRequest.sendRPC({
       
   321 				url : url,
       
   322 				method : m,
       
   323 				params : p,
       
   324 				success : cb,
       
   325 				error : function(e, x) {
       
   326 					t.editor.setProgressState(0);
       
   327 					t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText));
       
   328 				}
       
   329 			});
       
   330 		}
       
   331 	});
       
   332 
       
   333 	// Register plugin
       
   334 	tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin);
       
   335 })();