@@ -222,31 +222,48 @@ angular.module('textAngular.DOM', ['textAngular.factories'])
222
222
} catch ( e ) { }
223
223
} ;
224
224
} ;
225
- } ] ) . service ( 'taSelection' , [ '$window' , '$document' ,
225
+ } ] ) . service ( 'taSelection' , [ '$window' , '$document' , 'taDOM' ,
226
226
/* istanbul ignore next: all browser specifics and PhantomJS dosen't seem to support half of it */
227
- function ( $window , $document ) {
227
+ function ( $window , $document , taDOM ) {
228
228
// need to dereference the document else the calls don't work correctly
229
229
var _document = $document [ 0 ] ;
230
230
var rangy = $window . rangy ;
231
+ var brException = function ( element , offset ) {
232
+ /* check if selection is a BR element at the beginning of a container. If so, get
233
+ * the parentNode instead.
234
+ * offset should be zero in this case. Otherwise, return the original
235
+ * element.
236
+ */
237
+ if ( element . tagName && element . tagName . match ( / ^ b r $ / i) && offset === 0 && ! element . previousSibling ) {
238
+ return {
239
+ element : element . parentNode ,
240
+ offset : 0
241
+ } ;
242
+ } else {
243
+ return {
244
+ element : element ,
245
+ offset : offset
246
+ } ;
247
+ }
248
+ } ;
231
249
var api = {
232
250
getSelection : function ( ) {
233
251
var range = rangy . getSelection ( ) . getRangeAt ( 0 ) ;
234
252
var container = range . commonAncestorContainer ;
235
- // Check if the container is a text node and return its parent if so
236
- container = container . nodeType === 3 ? container . parentNode : container ;
237
- return {
238
- start : {
239
- element : range . startContainer ,
240
- offset : range . startOffset
241
- } ,
242
- end : {
243
- element : range . endContainer ,
244
- offset : range . endOffset
245
- } ,
246
- container : container ,
253
+ var selection = {
254
+ start : brException ( range . startContainer , range . startOffset ) ,
255
+ end : brException ( range . endContainer , range . endOffset ) ,
247
256
collapsed : range . collapsed
248
-
249
257
} ;
258
+ // Check if the container is a text node and return its parent if so
259
+ container = container . nodeType === 3 ? container . parentNode : container ;
260
+ if ( container . parentNode === selection . start . element ||
261
+ container . parentNode === selection . end . element ) {
262
+ selection . container = container . parentNode ;
263
+ } else {
264
+ selection . container = container ;
265
+ }
266
+ return selection ;
250
267
} ,
251
268
getOnlySelectedElements : function ( ) {
252
269
var range = rangy . getSelection ( ) . getRangeAt ( 0 ) ;
@@ -304,12 +321,13 @@ function($window, $document){
304
321
// from http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
305
322
// topNode is the contenteditable normally, all manipulation MUST be inside this.
306
323
insertHtml : function ( html , topNode ) {
307
- var parent , secondParent , _childI , nodes , startIndex , startNodes , endNodes , i , lastNode ;
324
+ var parent , secondParent , _childI , nodes , startIndex , startNodes , endNodes , i , lastNode , _tempFrag ;
308
325
var element = angular . element ( "<div>" + html + "</div>" ) ;
309
326
var range = rangy . getSelection ( ) . getRangeAt ( 0 ) ;
310
327
var frag = _document . createDocumentFragment ( ) ;
311
328
var children = element [ 0 ] . childNodes ;
312
329
var isInline = true ;
330
+
313
331
if ( children . length > 0 ) {
314
332
// NOTE!! We need to do the following:
315
333
// check for blockelements - if they exist then we have to split the current element in half (and all others up to the closest block element) and insert all children in-between.
@@ -336,41 +354,68 @@ function($window, $document){
336
354
if ( isInline ) {
337
355
range . deleteContents ( ) ;
338
356
} else { // not inline insert
339
- if ( range . collapsed && range . startContainer !== topNode && range . startContainer . parentNode !== topNode ) {
340
- // split element into 2 and insert block element in middle
341
- if ( range . startContainer . nodeType === 3 ) { // if text node
342
- parent = range . startContainer . parentNode ;
343
- nodes = parent . childNodes ;
344
- // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
345
- startNodes = [ ] ;
346
- endNodes = [ ] ;
347
- for ( startIndex = 0 ; startIndex < nodes . length ; startIndex ++ ) {
348
- startNodes . push ( nodes [ startIndex ] ) ;
349
- if ( nodes [ startIndex ] === range . startContainer ) break ;
357
+ if ( range . collapsed && range . startContainer !== topNode ) {
358
+ if ( range . startContainer . innerHTML && range . startContainer . innerHTML . match ( / ^ < [ ^ > ] * > $ / i) ) {
359
+ // this log is to catch when innerHTML is something like `<img ...>`
360
+ parent = range . startContainer ;
361
+ if ( range . startOffset === 1 ) {
362
+ // before single tag
363
+ range . setStartAfter ( parent ) ;
364
+ range . setEndAfter ( parent ) ;
365
+ } else {
366
+ // after single tag
367
+ range . setStartBefore ( parent ) ;
368
+ range . setEndBefore ( parent ) ;
350
369
}
351
- endNodes . push ( _document . createTextNode ( range . startContainer . nodeValue . substring ( range . startOffset ) ) ) ;
352
- range . startContainer . nodeValue = range . startContainer . nodeValue . substring ( 0 , range . startOffset ) ;
353
- for ( i = startIndex + 1 ; i < nodes . length ; i ++ ) endNodes . push ( nodes [ i ] ) ;
354
-
355
- secondParent = parent . cloneNode ( ) ;
356
- parent . childNodes = startNodes ;
357
- secondParent . childNodes = endNodes ;
358
370
} else {
359
- parent = range . startContainer ;
360
- secondParent = parent . cloneNode ( ) ;
361
- secondParent . innerHTML = parent . innerHTML . substring ( range . startOffset ) ;
362
- parent . innerHTML = parent . innerHTML . substring ( 0 , range . startOffset ) ;
363
- }
364
- angular . element ( parent ) . after ( secondParent ) ;
365
- // put cursor to end of inserted content
366
- range . setStartAfter ( parent ) ;
367
- range . setEndAfter ( parent ) ;
368
- if ( / ^ ( | < b r ( | \/ ) > ) $ / i. test ( parent . innerHTML . trim ( ) ) ) {
369
- range . setStartBefore ( parent ) ;
370
- range . setEndBefore ( parent ) ;
371
- angular . element ( parent ) . remove ( ) ;
371
+ // split element into 2 and insert block element in middle
372
+ if ( range . startContainer . nodeType === 3 && range . startContainer . parentNode !== topNode ) { // if text node
373
+ parent = range . startContainer . parentNode ;
374
+ secondParent = parent . cloneNode ( ) ;
375
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
376
+ taDOM . splitNodes ( parent . childNodes , parent , secondParent , range . startContainer , range . startOffset ) ;
377
+
378
+ // Escape out of the inline tags like b
379
+ while ( ! VALIDELEMENTS . test ( parent . nodeName ) ) {
380
+ angular . element ( parent ) . after ( secondParent ) ;
381
+ parent = parent . parentNode ;
382
+ var _lastSecondParent = secondParent ;
383
+ secondParent = parent . cloneNode ( ) ;
384
+ // split the nodes into two lists - before and after, splitting the node with the selection into 2 text nodes.
385
+ taDOM . splitNodes ( parent . childNodes , parent , secondParent , _lastSecondParent ) ;
386
+ }
387
+ } else {
388
+ parent = range . startContainer ;
389
+ secondParent = parent . cloneNode ( ) ;
390
+ taDOM . splitNodes ( parent . childNodes , parent , secondParent , undefined , undefined , range . startOffset ) ;
391
+ }
392
+
393
+ angular . element ( parent ) . after ( secondParent ) ;
394
+ // put cursor to end of inserted content
395
+ range . setStartAfter ( parent ) ;
396
+ range . setEndAfter ( parent ) ;
397
+
398
+ if ( / ^ ( | < b r ( | \/ ) > ) $ / i. test ( parent . innerHTML . trim ( ) ) ) {
399
+ range . setStartBefore ( parent ) ;
400
+ range . setEndBefore ( parent ) ;
401
+ angular . element ( parent ) . remove ( ) ;
402
+ }
403
+ if ( / ^ ( | < b r ( | \/ ) > ) $ / i. test ( secondParent . innerHTML . trim ( ) ) ) angular . element ( secondParent ) . remove ( ) ;
404
+ if ( parent . nodeName . toLowerCase ( ) === 'li' ) {
405
+ _tempFrag = _document . createDocumentFragment ( ) ;
406
+ for ( i = 0 ; i < frag . childNodes . length ; i ++ ) {
407
+ element = angular . element ( '<li>' ) ;
408
+ taDOM . transferChildNodes ( frag . childNodes [ i ] , element [ 0 ] ) ;
409
+ taDOM . transferNodeAttributes ( frag . childNodes [ i ] , element [ 0 ] ) ;
410
+ _tempFrag . appendChild ( element [ 0 ] ) ;
411
+ }
412
+ frag = _tempFrag ;
413
+ if ( lastNode ) {
414
+ lastNode = frag . childNodes [ frag . childNodes . length - 1 ] ;
415
+ lastNode = lastNode . childNodes [ lastNode . childNodes . length - 1 ] ;
416
+ }
417
+ }
372
418
}
373
- if ( / ^ ( | < b r ( | \/ ) > ) $ / i. test ( secondParent . innerHTML . trim ( ) ) ) angular . element ( secondParent ) . remove ( ) ;
374
419
} else {
375
420
range . deleteContents ( ) ;
376
421
}
@@ -382,4 +427,53 @@ function($window, $document){
382
427
}
383
428
} ;
384
429
return api ;
385
- } ] ) ;
430
+ } ] ) . service ( 'taDOM' , function ( ) {
431
+ var taDOM = {
432
+ // recursive function that returns an array of angular.elements that have the passed attribute set on them
433
+ getByAttribute : function ( element , attribute ) {
434
+ var resultingElements = [ ] ;
435
+ var childNodes = element . children ( ) ;
436
+ if ( childNodes . length ) {
437
+ angular . forEach ( childNodes , function ( child ) {
438
+ resultingElements = resultingElements . concat ( taDOM . getByAttribute ( angular . element ( child ) , attribute ) ) ;
439
+ } ) ;
440
+ }
441
+ if ( element . attr ( attribute ) !== undefined ) resultingElements . push ( element ) ;
442
+ return resultingElements ;
443
+ } ,
444
+
445
+ transferChildNodes : function ( source , target ) {
446
+ // clear out target
447
+ target . innerHTML = '' ;
448
+ while ( source . childNodes . length > 0 ) target . appendChild ( source . childNodes [ 0 ] ) ;
449
+ return target ;
450
+ } ,
451
+
452
+ splitNodes : function ( nodes , target1 , target2 , splitNode , subSplitIndex , splitIndex ) {
453
+ if ( ! splitNode && isNaN ( splitIndex ) ) throw new Error ( 'taDOM.splitNodes requires a splitNode or splitIndex' ) ;
454
+ var startNodes = document . createDocumentFragment ( ) ;
455
+ var endNodes = document . createDocumentFragment ( ) ;
456
+ var index = 0 ;
457
+
458
+ while ( nodes . length > 0 && ( isNaN ( splitIndex ) || splitIndex !== index ) && nodes [ 0 ] !== splitNode ) {
459
+ startNodes . appendChild ( nodes [ 0 ] ) ; // this removes from the nodes array (if proper childNodes object.
460
+ index ++ ;
461
+ }
462
+
463
+ if ( ! isNaN ( subSplitIndex ) && subSplitIndex >= 0 && nodes [ 0 ] ) {
464
+ startNodes . appendChild ( document . createTextNode ( nodes [ 0 ] . nodeValue . substring ( 0 , subSplitIndex ) ) ) ;
465
+ nodes [ 0 ] . nodeValue = nodes [ 0 ] . nodeValue . substring ( subSplitIndex ) ;
466
+ }
467
+ while ( nodes . length > 0 ) endNodes . appendChild ( nodes [ 0 ] ) ;
468
+
469
+ taDOM . transferChildNodes ( startNodes , target1 ) ;
470
+ taDOM . transferChildNodes ( endNodes , target2 ) ;
471
+ } ,
472
+
473
+ transferNodeAttributes : function ( source , target ) {
474
+ for ( var i = 0 ; i < source . attributes . length ; i ++ ) target . setAttribute ( source . attributes [ i ] . name , source . attributes [ i ] . value ) ;
475
+ return target ;
476
+ }
477
+ } ;
478
+ return taDOM ;
479
+ } ) ;
0 commit comments