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