Subversion-Projekte lars-tiefland.content-management

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
})();