PageRenderTime 42ms CodeModel.GetById 18ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/mod_base/lib/js/modules/tinymce3.4.3.2/plugins/spellchecker/editor_plugin_src.js

http://github.com/zotonic/zotonic
JavaScript | 434 lines | 385 code | 32 blank | 17 comment | 28 complexity | 17febdebb1a6a3037f104dafe49f3860 MD5 | raw file
  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					ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", '');
318					m.add({
319						title : 'spellchecker.ignore_word',
320						onclick : function() {
321							var word = wordSpan.innerHTML;
322
323							dom.remove(wordSpan, 1);
324							t._checkDone();
325
326							// tell the server if we need to
327							if (ignoreRpc) {
328								ed.setProgressState(1);
329								t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) {
330									ed.setProgressState(0);
331								});
332							}
333						}
334					});
335
336					m.add({
337						title : 'spellchecker.ignore_words',
338						onclick : function() {
339							var word = wordSpan.innerHTML;
340
341							t._removeWords(dom.decode(word));
342							t._checkDone();
343
344							// tell the server if we need to
345							if (ignoreRpc) {
346								ed.setProgressState(1);
347								t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) {
348									ed.setProgressState(0);
349								});
350							}
351						}
352					});
353
354					if (t.editor.getParam("spellchecker_enable_learn_rpc")) {
355						m.add({
356							title : 'spellchecker.learn_word',
357							onclick : function() {
358								var word = wordSpan.innerHTML;
359
360								dom.remove(wordSpan, 1);
361								t._checkDone();
362
363								ed.setProgressState(1);
364								t._sendRPC('learnWord', [t.selectedLang, word], function(r) {
365									ed.setProgressState(0);
366								});
367							}
368						});
369					}
370
371					m.update();
372				});
373
374				p1 = DOM.getPos(ed.getContentAreaContainer());
375				m.settings.offset_x = p1.x;
376				m.settings.offset_y = p1.y;
377
378				ed.selection.select(wordSpan);
379				p1 = dom.getPos(wordSpan);
380				m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y);
381
382				return tinymce.dom.Event.cancel(e);
383			} else
384				m.hideMenu();
385		},
386
387		_checkDone : function() {
388			var t = this, ed = t.editor, dom = ed.dom, o;
389
390			each(dom.select('span'), function(n) {
391				if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) {
392					o = true;
393					return false;
394				}
395			});
396
397			if (!o)
398				t._done();
399		},
400
401		_done : function() {
402			var t = this, la = t.active;
403
404			if (t.active) {
405				t.active = 0;
406				t._removeWords();
407
408				if (t._menu)
409					t._menu.hideMenu();
410
411				if (la)
412					t.editor.nodeChanged();
413			}
414		},
415
416		_sendRPC : function(m, p, cb) {
417			var t = this;
418
419			JSONRequest.sendRPC({
420				url : t.rpcUrl,
421				method : m,
422				params : p,
423				success : cb,
424				error : function(e, x) {
425					t.editor.setProgressState(0);
426					t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText));
427				}
428			});
429		}
430	});
431
432	// Register plugin
433	tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin);
434})();