comparison js/tinymce/atomic/editor_plugin_src.js @ 29:37ca57016cbe

Added TinyMCE atomic selection plugin.
author David Eads <eads@chicagotech.org>
date Tue, 17 Mar 2009 10:59:50 -0500
parents
children
comparison
equal deleted inserted replaced
28:7d6bf2dca269 29:37ca57016cbe
1 /**
2 *
3 * @author Sander Kruger
4 * @copyright Copyright © 2009, Sander Kruger, All rights reserved.
5 */
6
7 (function() {
8 var Event = tinymce.dom.Event;
9 var direction = 0; // Track cursor direction for skipping
10 var hasClicked = false; // Track mouseclicks for selecting
11 var atStart = null; // Cache if the cursor is at the start or at the end of
12 var atEnd = null; // an atom.
13
14 tinymce.create('tinymce.plugins.AtomicPlugin', {
15 init : function(ed, url) {
16 var t = this, atomicClass;
17
18 t.editor = ed;
19 atomicClass = ed.getParam("atomic_atomic_class", "mceAtomic");
20
21 ed.onNodeChange.addToTop(function(ed, cm, n) {
22 var sc, ec, wasEnd=false;
23
24 // Check if the start or end of the selection is in an atom
25 sc = ed.dom.getParent(ed.selection.getStart(), function(n) {
26 return ed.dom.hasClass(n, atomicClass);
27 });
28
29 ec = ed.dom.getParent(ed.selection.getEnd(), function(n) {
30 return ed.dom.hasClass(n, atomicClass);
31 });
32 if (atEnd)
33 {
34 wasEnd = true;
35 }
36 atStart = null;
37 atEnd = null;
38 // Check situation to move the cursor or selection.
39 if (sc || ec)
40 {
41 var s = ed.selection.getSel();
42 var r = ed.selection.getRng();
43 var select = s.type?(s.type!="None"):(s.anchorNode != s.focusNode || s.anchorOffset != s.focusOffset);
44 var move = false;
45 if (!select)
46 {
47 // The user has not selected anything but is moving the
48 // cursor or clicked on an atom.
49 if (direction == -1)
50 {
51 if (r.setStart)
52 {
53 var lc = t._leftNeighbour( ed, sc );
54 if (lc)
55 {
56 // Make sure that when skipping to the left,
57 // the next input doesn't go into the atom.
58 var offset = lc.nodeType==3?lc.length:1;
59 r.setStart( lc, offset );
60 r.setEnd( lc, offset );
61 }
62 else
63 {
64 r.setStart( sc, 0 );
65 r.setEnd( sc, 0 );
66 }
67 atStart = sc;
68 move = true;
69 }
70 else
71 {
72 // ie (check if the caret is not just after the
73 // atom)
74 var r2 = ed.getBody().createTextRange();
75 r2.moveToElementText( ec );
76 r2.collapse(false);
77 if (!r2.isEqual( r ))
78 {
79 r.moveToElementText( sc );
80 r.collapse();
81 atStart = sc;
82 move = true;
83 }
84 else
85 {
86 var tempEl = ed.dom.create("span", null, "&shy;");
87 ec.insertAdjacentElement("afterEnd", tempEl);
88
89 r.moveToElementText(tempEl);
90 r.collapse(false);
91 ed.selection.setRng(r);
92
93 ed.dom.remove(tempEl);
94 atEnd = ec;
95 }
96 }
97 }
98 else if (direction == 1)
99 {
100 if (r.setStartAfter)
101 {
102 r.setStartAfter( ec );
103 r.setEndAfter( ec );
104 move = true;
105 }
106 else
107 {
108 if (wasEnd)
109 {
110 r.move( 'character' );
111 ed.selection.setRng(r);
112 ec = null;
113 }
114 else
115 {
116 var tempEl = ed.dom.create("span", null, "&shy;");
117 ec.insertAdjacentElement("afterEnd", tempEl);
118
119 r.moveToElementText(tempEl);
120 r.collapse(false);
121 ed.selection.setRng(r);
122
123 ed.dom.remove(tempEl);
124 }
125 }
126 atEnd = ec;
127 }
128 else if (hasClicked)
129 {
130 if (r.setStart)
131 {
132 r.setStart( sc, 0 );
133 r.setEnd( ec, ec.childNodes.length );
134 }
135 else
136 {
137 // ie
138 r.moveToElementText( sc );
139 }
140 move = true;
141 }
142 else if (wasEnd && !r.setStart)
143 {
144 // ie
145 var tempEl = ed.dom.create("span", null, "&shy;");
146 ec.insertAdjacentElement("afterEnd", tempEl);
147
148 r.moveToElementText(tempEl);
149 r.collapse(false);
150 ed.selection.setRng(r);
151
152 ed.dom.remove(tempEl);
153 atEnd = ec;
154 }
155 }
156 else
157 {
158 // Reverse selections have the anchor at the end and the
159 // focus at the start. This happens when selecting
160 // backwards. Make sure the extended selection keeps the
161 // same direction.
162 // *** Issue: ie doesn't support backward selections
163 // programmatically (or at least I have not found how to
164 // do this).
165 var reverse = false;
166 if (s.anchorNode && s.anchorNode == r.endContainer && s.anchorOffset == r.endOffset)
167 {
168 reverse = true;
169 }
170 var moveStart = false;
171 var moveEnd = false;
172 var rsc, rec;
173 if (sc)
174 {
175 if (s.anchorNode)
176 {
177 if (sc != r.startContainer || (r.startOffset != 0 && r.startOffset != sc.childNodes.length))
178 {
179 // The start pos is not where it should be.
180 moveStart = true;
181 }
182 }
183 else
184 {
185 // ie
186 rsc = ed.getBody().createTextRange();
187 rsc.moveToElementText( sc );
188 moveStart = (r.compareEndPoints( "StartToStart", rsc ) != 0);
189 }
190 }
191 if (ec)
192 {
193 if (s.anchorNode)
194 {
195 if (ec != r.endContainer || (r.endOffset != 0 && r.endOffset != ec.childNodes.length))
196 {
197 // The end pos is not where it should be
198 moveEnd = true;
199 }
200 }
201 else
202 {
203 // ie
204 rec = ed.getBody().createTextRange();
205 rec.moveToElementText( ec );
206 moveEnd = (r.compareEndPoints( "EndToEnd", rec ) != 0);
207 }
208 }
209 if (reverse)
210 {
211 // Start with the end. Since 'selection direction detection'
212 // doesn't work on IE, don't bother with the IE code here.
213 if (moveEnd)
214 {
215 // If both start and end move and they move leftwards, set start first and then extend instead of starting with setend
216 if (direction == -1)
217 {
218 r.setEnd( ec, 0 );
219 }
220 else
221 {
222 r.setEnd( ec, ec.childNodes.length );
223 }
224 move = true;
225 }
226 if (moveStart)
227 {
228 // Use the extend method to advance the focus
229 // position of the selection.
230 if (direction == 1)
231 {
232 s.extend( sc, sc.childNodes.length );
233 move = false;
234 }
235 else
236 {
237 s.extend( sc, 0 );
238 move = false;
239 }
240 }
241 }
242 else
243 {
244 if (moveStart)
245 {
246 if (direction == 1)
247 {
248 if (r.setStart)
249 {
250 r.setStart( sc, sc.childNodes.length );
251 }
252 else
253 {
254 // ie
255 r.setEndPoint( "StartToEnd", rsc );
256 }
257 }
258 else
259 {
260 if (r.setStart)
261 {
262 r.setStart( sc, 0 );
263 }
264 else
265 {
266 // ie
267 r.setEndPoint( "StartToStart", rsc );
268 }
269 }
270 move = true;
271 }
272 if (moveEnd)
273 {
274 if (direction == -1)
275 {
276 if (r.setEnd)
277 {
278 s.extend( ec, 0 );
279 }
280 else
281 {
282 // ie
283 r.setEndPoint( "EndToStart", rec );
284 move = true;
285 }
286 }
287 else
288 {
289 if (r.setEnd)
290 {
291 s.extend( ec, ec.childNodes.length );
292 }
293 else
294 {
295 // ie
296 r.setEndPoint( "EndToEnd", rec );
297 move = true;
298 }
299 }
300 // move = false;
301 }
302 }
303 }
304 if (move)
305 {
306 // Make sure other modules can react to this event.
307 ed.selection.setRng( r );
308 }
309 }
310 });
311
312 ed.onKeyDown.addToTop( function(ed, e) {
313 var k = e.keyCode, atomicClass;
314 atomicClass = ed.getParam("atomic_atomic_class", "mceAtomic");
315 hasClicked = false;
316 direction = 0;
317 if (k == 37 || k == 38)
318 {
319 direction = -1;
320 }
321 else if (k == 39 || k == 40)
322 {
323 direction = 1;
324 }
325 else if (k == 8)
326 {
327 // Check if an atom is being 'backspace'd
328 if (!atEnd)
329 {
330 var s = ed.selection.getSel();
331 var select = s.type?(s.type!="None"):(s.anchorNode != s.focusNode || s.anchorOffset != s.focusOffset);
332 if (!select)
333 {
334 var ep = s.focusNode?((s.focusNode.nodeType == 3 && s.focusOffset != 0)?null:s.focusNode):ed.selection.getStart();
335 if (ed.dom.hasClass(ep, atomicClass))
336 {
337 atEnd = ep;
338 }
339 else if (ep)
340 {
341 var rc;
342 if (!s.focusNode)
343 {
344 // ie
345 // focusOffset is not given. So, we need to find
346 // it by walking through the children and comparing
347 // end points.
348 var r = ed.selection.getRng();
349 var r2 = ed.getBody().createTextRange();
350 r2.moveToElementText( ep );
351 if (r2.compareEndPoints( "StartToStart", r ) == 0)
352 {
353 // The caret is at the start of this element.
354 // Find the left neighbouring element
355 rc = t._leftNeighbour( ed, ep );
356 }
357 else
358 {
359 // The focus is not at the start of the parent.
360 // Find the child left of the caret.
361 rc = ep.firstChild;
362 while (rc)
363 {
364 // We are not interested in text nodes
365 // because they cannot be atoms (all by
366 // themselves)
367 if (rc.nodeType == 1)
368 {
369 r2.moveToElementText( rc );
370 if (r.compareEndPoints( "EndToEnd", r2 ) == 0)
371 {
372 break;
373 }
374 }
375 rc = rc.nextSibling;
376 }
377 }
378 }
379 else
380 {
381 if (ep.nodeType == 1 && s.focusOffset != 0)
382 {
383 // ep encloses the current caret position.
384 // select the child to the left of the caret.
385 rc = ep.childNodes[s.focusOffset-1];
386 }
387 else
388 {
389 // The caret is on the left-most end of ep.
390 // Find it's left neighbour.
391 rc = t._leftNeighbour( ed, ep );
392 }
393 }
394 if (rc)
395 {
396 // Now see if this element has a right-most
397 // descendent that is an atom
398 while (rc && !ed.dom.hasClass(rc, atomicClass))
399 {
400 rc = rc.lastChild;
401 }
402 if (rc)
403 {
404 atEnd = rc;
405 }
406 }
407 }
408 }
409 }
410 if (atEnd)
411 {
412 ed.dom.remove( atEnd );
413 atEnd = null;
414 return Event.cancel(e);
415 }
416 }
417 else if (k == 46)
418 {
419 // Check if an atom is being 'delete'd
420 if (!atStart)
421 {
422 var s = ed.selection.getSel();
423 var select = s.type?(s.type!="None"):(s.anchorNode != s.focusNode || s.anchorOffset != s.focusOffset);
424 if (!select)
425 {
426 var ep = s.focusNode?((s.focusNode.nodeType == 3 && s.focusOffset != s.focusNode.length)?null:s.focusNode):ed.selection.getEnd();
427 if (ed.dom.hasClass(ep, atomicClass))
428 {
429 atStart = ep;
430 }
431 else if (ep)
432 {
433 var lc;
434 if (!s.focusNode)
435 {
436 // ie
437 // focusOffset is not given. So, we need to find
438 // it by walking through the children and comparing
439 // end points.
440 var r = ed.selection.getRng();
441 var r2 = ed.getBody().createTextRange();
442 r2.moveToElementText( ep );
443 if (r2.compareEndPoints( "EndToEnd", r ) == 0)
444 {
445 // The caret is at the start of this element.
446 // Find the right neighbouring element
447 lc = t._rightNeighbour( ed, ep );
448 }
449 else
450 {
451 // The focus is not at the end of the parent.
452 // Find the child right of the caret.
453 lc = ep.firstChild;
454 while (lc)
455 {
456 // We are not interested in text nodes
457 // because they cannot be atoms (all by
458 // themselves)
459 if (lc.nodeType == 1)
460 {
461 r2.moveToElementText( lc );
462 if (r.compareEndPoints( "StartToStart", r2 ) == 0)
463 {
464 break;
465 }
466 }
467 lc = lc.nextSibling;
468 }
469 }
470 }
471 else
472 {
473 if (ep.nodeType == 1 && s.focusOffset != ep.childNodes.length)
474 {
475 // ep encloses the current caret position.
476 // select the child to the right of the caret.
477 lc = ep.childNodes[s.focusOffset];
478 }
479 else
480 {
481 // The caret is on the right-most end of ep.
482 // Find it's right neighbour.
483 lc = t._rightNeighbour( ed, ep );
484 }
485 }
486 if (lc)
487 {
488 // Now see if this element has a left-most
489 // descendent that is an atom
490 while (lc && !ed.dom.hasClass(lc, atomicClass))
491 {
492 lc = lc.firstChild;
493 }
494 if (lc)
495 {
496 atStart = lc;
497 }
498 }
499 }
500 }
501 }
502 if (atStart)
503 {
504 ed.dom.remove( atStart );
505 atStart = null;
506 return Event.cancel(e);
507 }
508 }
509 });
510
511 ed.onMouseDown.addToTop(t._onClick);
512 ed.onMouseUp.addToTop(t._onClick);
513 },
514
515 getInfo : function() {
516 return {
517 longname : 'Atomic elements',
518 author : 'Sander Kruger',
519 authorurl : 'http://www.3gsp.eu',
520 infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/atomic',
521 version : tinymce.majorVersion + "." + tinymce.minorVersion
522 };
523 },
524
525 _onClick : function(ed, e) {
526 direction = 0;
527 hasClicked = true;
528 atStart = null;
529 atEnd = null;
530 },
531
532 // Helper methods
533 _leftNeighbour : function( ed, e ) {
534 var l = ed.dom.getParent(e, function(n) {
535 return e.previousSibling != null;
536 });
537 if (l)
538 {
539 return l.previousSibling;
540 }
541 return null;
542 },
543
544 _rightNeighbour : function( ed, e ) {
545 var r = ed.dom.getParent(e, function(n) {
546 return e.nextSibling != null;
547 });
548 if (r)
549 {
550 return r.nextSibling;
551 }
552 return null;
553 }
554 });
555
556 // Register plugin
557 tinymce.PluginManager.add('atomic', tinymce.plugins.AtomicPlugin);
558 })();