Mercurial > defr > drupal > scald > dnd
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, "­"); | |
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, "­"); | |
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, "­"); | |
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 })(); |