Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

summernote / summernote / 3814

21 Nov 2019 - 1:41 coverage increased (+0.002%) to 78.214%
3814

Pull #3498

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
fixed not using p-br tag
Pull Request #3498: fixed not using p-br tag

1053 of 1897 branches covered (55.51%)

13 of 14 new or added lines in 3 files covered. (92.86%)

39 existing lines in 3 files now uncovered.

4660 of 5958 relevant lines covered (78.21%)

630.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.62
/src/js/base/core/dom.js
1
import $ from 'jquery';
6×
2
import func from './func';
6×
3
import lists from './lists';
6×
4
import env from './env';
6×
5

6×
6
const NBSP_CHAR = String.fromCharCode(160);
6×
7
const ZERO_WIDTH_NBSP_CHAR = '\ufeff';
6×
8

6×
9
/**
10
 * @method isEditable
11
 *
12
 * returns whether node is `note-editable` or not.
13
 *
14
 * @param {Node} node
15
 * @return {Boolean}
16
 */
17
function isEditable(node) {
12×
18
  return node && $(node).hasClass('note-editable');
25,792×
19
}
20

21
/**
22
 * @method isControlSizing
23
 *
24
 * returns whether node is `note-control-sizing` or not.
25
 *
26
 * @param {Node} node
27
 * @return {Boolean}
28
 */
12×
29
function isControlSizing(node) {
Branches [[2, 0], [2, 1]] missed. !
30
  return node && $(node).hasClass('note-control-sizing');
31
}
32

33
/**
34
 * @method makePredByNodeName
35
 *
36
 * returns predicate which judge whether nodeName is same
37
 *
38
 * @param {String} nodeName
39
 * @return {Function}
12×
40
 */
147×
41
function makePredByNodeName(nodeName) {
147×
42
  nodeName = nodeName.toUpperCase();
13,617×
43
  return function(node) {
44
    return node && node.nodeName.toUpperCase() === nodeName;
45
  };
46
}
47

48
/**
49
 * @method isText
50
 *
51
 *
52
 *
53
 * @param {Node} node
12×
54
 * @return {Boolean} true if node's type is text(3)
18,195×
55
 */
56
function isText(node) {
57
  return node && node.nodeType === 3;
58
}
59

60
/**
61
 * @method isElement
62
 *
63
 *
64
 *
12×
65
 * @param {Node} node
264×
66
 * @return {Boolean} true if node's type is element(1)
67
 */
68
function isElement(node) {
69
  return node && node.nodeType === 1;
70
}
71

12×
72
/**
4,264×
73
 * ex) br, col, embed, hr, img, input, ...
74
 * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
12×
75
 */
4,310×
76
function isVoid(node) {
297×
77
  return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON|^INPUT|^AUDIO|^VIDEO|^EMBED/.test(node.nodeName.toUpperCase());
78
}
79

4,013×
80
function isPara(node) {
81
  if (isEditable(node)) {
12×
82
    return false;
12×
83
  }
84

6×
85
  // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
6×
86
  return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
12×
87
}
30×
88

89
function isHeading(node) {
6×
90
  return node && /^H[1-7]/.test(node.nodeName.toUpperCase());
6×
91
}
12×
92

1,866×
93
const isPre = makePredByNodeName('PRE');
94

95
const isLi = makePredByNodeName('LI');
96

97
function isPurePara(node) {
98
  return isPara(node) && !isLi(node);
99
}
100

12×
101
const isTable = makePredByNodeName('TABLE');
2,497×
102

103
const isData = makePredByNodeName('DATA');
6×
104

12×
105
function isInline(node) {
2,663×
106
  return !isBodyContainer(node) &&
107
         !isList(node) &&
6×
108
         !isHr(node) &&
12×
109
         !isPara(node) &&
2,148×
110
         !isTable(node) &&
111
         !isBlockquote(node) &&
6×
112
         !isData(node);
12×
113
}
414×
114

115
function isList(node) {
12×
116
  return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
Branches [[16, 0], [16, 1]] missed. !
117
}
118

6×
119
const isHr = makePredByNodeName('HR');
120

121
function isCell(node) {
122
  return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
123
}
124

125
const isBlockquote = makePredByNodeName('BLOCKQUOTE');
126

12×
127
function isBodyContainer(node) {
Branches [[17, 0], [17, 1]] missed. !
128
  return isCell(node) || isBlockquote(node) || isEditable(node);
129
}
130

131
const isAnchor = makePredByNodeName('A');
132

133
function isParaInline(node) {
134
  return isInline(node) && !!ancestor(node, isPara);
135
}
136

137
function isBodyInline(node) {
12×
138
  return isInline(node) && !ancestor(node, isPara);
Branches [[18, 1]] missed. 12×
139
}
12×
140

Branches [[19, 0], [20, 1]] missed. 12×
141
const isBody = makePredByNodeName('BODY');
!
142

143
/**
12×
144
 * returns whether nodeB is closest sibling of nodeA
12×
145
 *
6×
146
 * @param {Node} nodeA
147
 * @param {Node} nodeB
12×
148
 * @return {Boolean}
149
 */
150
function isClosestSibling(nodeA, nodeB) {
151
  return nodeA.nextSibling === nodeB ||
152
         nodeA.previousSibling === nodeB;
153
}
154

Branches [[23, 0], [24, 1]] missed. 6×
155
/**
156
 * returns array of closest siblings with node
157
 *
158
 * @param {Node} node
159
 * @param {function} [pred] - predicate function
160
 * @return {Node[]}
161
 */
162
function withClosestSiblings(node, pred) {
12×
163
  pred = pred || func.ok;
9,971×
164

4,340×
165
  const siblings = [];
166
  if (node.previousSibling && pred(node.previousSibling)) {
Branches [[26, 1]] missed. 5,631×
167
    siblings.push(node.previousSibling);
5,631×
168
  }
169
  siblings.push(node);
!
170
  if (node.nextSibling && pred(node.nextSibling)) {
171
    siblings.push(node.nextSibling);
172
  }
173
  return siblings;
174
}
175

176
/**
177
 * blank HTML for cursor position
12×
178
 * - [workaround] old IE only works with  
!
179
 * - [workaround] IE11 and other browser works with bogus br
Branches [[27, 0], [27, 1], [28, 0], [28, 1]] missed. !
180
 */
!
181
const blankHTML = env.isMSIE && env.browserVersion < 11 ? '&nbsp;' : '<br>';
182

!
183
/**
184
 * @method nodeLength
185
 *
186
 * returns #text's text size or element's childNodes size
187
 *
188
 * @param {Node} node
189
 */
190
function nodeLength(node) {
12×
191
  if (isText(node)) {
3,510×
192
    return node.nodeValue.length;
3,510×
193
  }
42×
194

195
  if (node) {
3,468×
196
    return node.childNodes.length;
197
  }
90×
198

199
  return 0;
Branches [[32, 0]] missed. 3,378×
200
}
201

!
202
/**
203
 * returns whether deepest child node is empty or not.
3,378×
204
 *
205
 * @param {Node} node
206
 * @return {Boolean}
207
 */
208
function deepestChildIsEmpty(node) {
12×
209
  do {
264×
210
    if (node.firstElementChild === null || node.firstElementChild.innerHTML === '') break;
42×
211
  } while ((node = node.firstElementChild));
212

213
  return isEmpty(node);
214
}
215

216
/**
217
 * returns whether node is empty or not.
218
 *
219
 * @param {Node} node
12×
220
 * @return {Boolean}
6,532×
221
 */
13,880×
222
function isEmpty(node) {
5,339×
223
  const len = nodeLength(node);
224

8,541×
225
  if (len === 0) {
1,161×
226
    return true;
227
  } else if (!isText(node) && len === 1 && node.innerHTML === blankHTML) {
7,380×
228
    // ex) <p><br></p>, <span><br></span>
229
    return true;
1,193×
230
  } else if (lists.all(node.childNodes, isText) && node.innerHTML === '') {
231
    // ex) <p></p>, <span></span>
232
    return true;
233
  }
234

235
  return false;
236
}
237

12×
238
/**
57×
239
 * padding blankHTML if node is empty (for cursor position)
57×
240
 */
66×
241
function paddingBlankHTML(node) {
36×
242
  if (!isVoid(node) && !nodeLength(node)) {
243
    node.innerHTML = blankHTML;
30×
244
  }
12×
245
}
246

18×
247
/**
9×
248
 * find nearest ancestor predicate hit
249
 *
9×
250
 * @param {Node} node
251
 * @param {Function} pred - predicate function
45×
252
 */
253
function ancestor(node, pred) {
254
  while (node) {
255
    if (pred(node)) { return node; }
256
    if (isEditable(node)) { break; }
257

258
    node = node.parentNode;
259
  }
12×
260
  return null;
2,660×
261
}
2,660×
262

2,660×
263
/**
5,901×
264
 * find nearest ancestor only single child blood line and predicate hit
3,713×
265
 *
266
 * @param {Node} node
5,901×
267
 * @param {Function} pred - predicate function
268
 */
2,660×
269
function singleChildAncestor(node, pred) {
270
  node = node.parentNode;
271

272
  while (node) {
273
    if (nodeLength(node) !== 1) { break; }
12×
274
    if (pred(node)) { return node; }
27×
275
    if (isEditable(node)) { break; }
27×
276

277
    node = node.parentNode;
278
  }
279
  return null;
280
}
281

282
/**
283
 * returns new array of ancestor nodes (until predicate hit).
12×
284
 *
174×
285
 * @param {Node} node
174×
286
 * @param {Function} [optional] pred - predicate function
204×
287
 */
174×
288
function listAncestor(node, pred) {
289
  pred = pred || func.fail;
!
290

291
  const ancestors = [];
292
  ancestor(node, function(el) {
293
    if (!isEditable(el)) {
294
      ancestors.push(el);
295
    }
296

297
    return pred(el);
12×
298
  });
150×
299
  return ancestors;
150×
300
}
150×
301

192×
302
/**
66×
303
 * find farthest ancestor predicate hit
304
 */
126×
305
function lastAncestor(node, pred) {
126×
306
  const ancestors = listAncestor(node);
307
  return lists.last(ancestors.filter(pred));
150×
308
}
309

310
/**
311
 * returns common ancestor node between two nodes.
312
 *
313
 * @param {Node} nodeA
314
 * @param {Node} nodeB
315
 */
12×
316
function commonAncestor(nodeA, nodeB) {
381×
317
  const ancestors = listAncestor(nodeA);
381×
318
  for (let n = nodeB; n; n = n.parentNode) {
381×
319
    if (ancestors.indexOf(n) > -1) return n;
441×
320
  }
6×
321
  return null; // difference document area
322
}
435×
323

435×
324
/**
325
 * listing all previous siblings (until predicate hit).
381×
326
 *
327
 * @param {Node} node
328
 * @param {Function} [optional] pred - predicate function
329
 */
330
function listPrev(node, pred) {
331
  pred = pred || func.fail;
332

333
  const nodes = [];
12×
334
  while (node) {
45×
335
    if (pred(node)) { break; }
45×
336
    nodes.push(node);
337
    node = node.previousSibling;
45×
338
  }
135×
339
  return nodes;
39×
340
}
341

135×
342
/**
90×
343
 * listing next siblings (until predicate hit).
344
 *
345
 * @param {Node} node
45×
346
 * @param {Function} [pred] - predicate function
347
 */
348
function listNext(node, pred) {
349
  pred = pred || func.fail;
350

351
  const nodes = [];
352
  while (node) {
353
    if (pred(node)) { break; }
354
    nodes.push(node);
12×
355
    node = node.nextSibling;
111×
356
  }
111×
357
  return nodes;
111×
358
}
111×
359

111×
360
/**
361
 * listing descendant nodes
362
 *
363
 * @param {Node} node
364
 * @param {Function} [pred] - predicate function
365
 */
366
function listDescendant(node, pred) {
367
  const descendants = [];
12×
368
  pred = pred || func.ok;
276×
369

276×
370
  // start DFS(depth first search) with node
276×
371
  (function fnWalk(current) {
81×
372
    if (node !== current && pred(current)) {
373
      descendants.push(current);
374
    }
195×
375
    for (let idx = 0, len = current.childNodes.length; idx < len; idx++) {
376
      fnWalk(current.childNodes[idx]);
276×
377
    }
378
  })(node);
379

380
  return descendants;
381
}
382

383
/**
384
 * wrap node with new tag.
12×
385
 *
339×
386
 * @param {Node} node
459×
387
 * @param {Node} tagName of wrapper
388
 * @return {Node} - wrapper
339×
389
 */
390
function wrap(node, wrapperName) {
391
  const parent = node.parentNode;
392
  const wrapper = $('<' + wrapperName + '>')[0];
393

394
  parent.insertBefore(wrapper, node);
395
  wrapper.appendChild(node);
396

12×
397
  return wrapper;
2,591×
398
}
399

400
/**
401
 * insert node after preceding
402
 *
403
 * @param {Node} node
404
 * @param {Node} preceding - predicate function
405
 */
12×
406
function insertAfter(node, preceding) {
2,463×
407
  const next = preceding.nextSibling;
408
  let parent = preceding.parentNode;
409
  if (next) {
410
    parent.insertBefore(node, next);
411
  } else {
412
    parent.appendChild(node);
413
  }
414
  return node;
12×
415
}
1,231×
416

417
/**
418
 * append elements.
419
 *
420
 * @param {Node} node
421
 * @param {Collection} aChild
422
 */
423
function appendChildNodes(node, aChild) {
424
  $.each(aChild, function(idx, child) {
12×
425
    node.appendChild(child);
171×
426
  });
96×
427
  return node;
6×
428
}
429

90×
430
/**
431
 * returns whether boundaryPoint is left edge or not.
165×
432
 *
433
 * @param {BoundaryPoint} point
434
 * @return {Boolean}
435
 */
436
function isLeftEdgePoint(point) {
437
  return point.offset === 0;
438
}
439

440
/**
12×
441
 * returns whether boundaryPoint is right edge or not.
Branches [[55, 0]] missed. 255×
442
 *
!
443
 * @param {BoundaryPoint} point
444
 * @return {Boolean}
255×
445
 */
45×
446
function isRightEdgePoint(point) {
15×
447
  return point.offset === nodeLength(point.node);
448
}
30×
449

450
/**
240×
451
 * returns whether boundaryPoint is edge or not.
452
 *
453
 * @param {BoundaryPoint} point
454
 * @return {Boolean}
455
 */
456
function isEdgePoint(point) {
457
  return isLeftEdgePoint(point) || isRightEdgePoint(point);
458
}
12×
459

456×
460
/**
461
 * returns whether node is left edge of ancestor or not.
462
 *
463
 * @param {Node} node
464
 * @param {Node} ancestor
465
 * @return {Boolean}
466
 */
467
function isLeftEdgeOf(node, ancestor) {
12×
468
  while (node && node !== ancestor) {
456×
469
    if (position(node) !== 0) {
470
      return false;
471
    }
472
    node = node.parentNode;
473
  }
474

475
  return true;
12×
476
}
1,917×
477

1,917×
478
/**
1,122×
479
 * returns whether node is right edge of ancestor or not.
480
 *
1,917×
481
 * @param {Node} node
482
 * @param {Node} ancestor
12×
483
 * @return {Boolean}
2,788×
484
 */
485
function isRightEdgeOf(node, ancestor) {
486
  if (!ancestor) {
487
    return false;
488
  }
489
  while (node && node !== ancestor) {
490
    if (position(node) !== nodeLength(node.parentNode) - 1) {
491
      return false;
492
    }
12×
493
    node = node.parentNode;
1,131×
494
  }
1,131×
495

1,131×
496
  return true;
207×
497
}
18×
498

499
/**
189×
500
 * returns whether point is left edge of ancestor or not.
189×
501
 * @param {BoundaryPoint} point
502
 * @param {Node} ancestor
924×
503
 * @return {Boolean}
834×
504
 */
834×
505
function isLeftEdgePointOf(point, ancestor) {
506
  return isLeftEdgePoint(point) && isLeftEdgeOf(point.node, ancestor);
507
}
90×
508

Branches [[64, 0]] missed. 90×
509
/**
510
 * returns whether point is right edge of ancestor or not.
1,113×
511
 * @param {BoundaryPoint} point
512
 * @param {Node} ancestor
513
 * @return {Boolean}
514
 */
515
function isRightEdgePointOf(point, ancestor) {
516
  return isRightEdgePoint(point) && isRightEdgeOf(point.node, ancestor);
517
}
518

519
/**
520
 * returns offset from parent.
521
 *
522
 * @param {Node} node
12×
523
 */
1,365×
524
function position(node) {
Branches [[65, 0]] missed. 1,365×
UNCOV
525
  let offset = 0;
!
526
  while ((node = node.previousSibling)) {
527
    offset += 1;
1,365×
528
  }
354×
529
  return offset;
138×
530
}
531

216×
532
function hasChildren(node) {
216×
533
  return !!(node && node.childNodes && node.childNodes.length);
534
}
1,011×
535

396×
536
/**
396×
537
 * returns previous boundaryPoint
Branches [[69, 0]] missed. 396×
UNCOV
538
 *
!
539
 * @param {BoundaryPoint} point
540
 * @param {Boolean} isSkipInnerOffset
541
 * @return {BoundaryPoint}
542
 */
615×
543
function prevPoint(point, isSkipInnerOffset) {
615×
544
  let node;
Branches [[71, 0]] missed. 615×
UNCOV
545
  let offset;
!
546

547
  if (point.offset === 0) {
548
    if (isEditable(point.node)) {
1,227×
549
      return null;
550
    }
551

552
    node = point.node.parentNode;
553
    offset = position(point.node);
554
  } else if (hasChildren(point.node)) {
555
    node = point.node.childNodes[point.offset - 1];
556
    offset = nodeLength(node);
557
  } else {
558
    node = point.node;
559
    offset = isSkipInnerOffset ? 0 : point.offset - 1;
560
  }
12×
561

900×
562
  return {
563
    node: node,
564
    offset: offset,
565
  };
566
}
567

568
/**
569
 * returns next boundaryPoint
12×
570
 *
1,885×
571
 * @param {BoundaryPoint} point
1,114×
572
 * @param {Boolean} isSkipInnerOffset
573
 * @return {BoundaryPoint}
771×
574
 */
771×
575
function nextPoint(point, isSkipInnerOffset) {
771×
576
  let node, offset;
6×
577

578
  if (isEmpty(point.node)) {
765×
579
    return null;
580
  }
581

582
  if (nodeLength(point.node) === point.offset) {
583
    if (isEditable(point.node)) {
584
      return null;
585
    }
586

587
    node = point.node.parentNode;
12×
588
    offset = position(point.node) + 1;
321×
589
  } else if (hasChildren(point.node)) {
717×
590
    node = point.node.childNodes[point.offset];
321×
591
    offset = 0;
592
    if (isEmpty(node)) {
396×
593
      return null;
NEW
594
    }
!
595
  } else {
596
    node = point.node;
597
    offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1;
598

599
    if (isEmpty(node)) {
600
      return null;
601
    }
602
  }
603

12×
604
  return {
123×
605
    node: node,
180×
606
    offset: offset,
123×
607
  };
608
}
57×
609

UNCOV
610
/**
!
611
 * returns whether pointA and pointB is same or not.
612
 *
613
 * @param {BoundaryPoint} pointA
614
 * @param {BoundaryPoint} pointB
615
 * @return {Boolean}
616
 */
617
function isSamePoint(pointA, pointB) {
618
  return pointA.node === pointB.node && pointA.offset === pointB.offset;
12×
619
}
Branches [[79, 0]] missed. 126×
UNCOV
620

!
621
/**
622
 * returns whether point is visible (can set cursor) or not.
126×
623
 *
126×
624
 * @param {BoundaryPoint} point
625
 * @return {Boolean}
626
 */
627
function isVisiblePoint(point) {
628
  if (isText(point.node) || !hasChildren(point.node) || isEmpty(point.node)) {
629
    return true;
630
  }
631

632
  const leftNode = point.node.childNodes[point.offset - 1];
633
  const rightNode = point.node.childNodes[point.offset];
12×
634
  if ((!leftNode || isVoid(leftNode)) && (!rightNode || isVoid(rightNode))) {
171×
635
    return true;
171×
636
  }
900×
637

900×
638
  return false;
171×
639
}
640

729×
641
/**
642
 * @method prevPointUtil
643
 *
729×
644
 * @param {BoundaryPoint} point
645
 * @param {Function} pred
646
 * @return {BoundaryPoint}
647
 */
648
function prevPointUntil(point, pred) {
649
  while (point) {
650
    if (pred(point)) {
651
      return point;
652
    }
653

654
    point = prevPoint(point);
12×
655
  }
1,712×
656

1,712×
657
  return null;
658
}
659

660
/**
661
 * @method nextPointUntil
662
 *
663
 * @param {BoundaryPoint} point
664
 * @param {Function} pred
665
 * @return {BoundaryPoint}
666
 */
12×
667
function nextPointUntil(point, pred) {
84×
668
  while (point) {
84×
669
    if (pred(point)) {
Branches [[83, 0]] missed. 96×
UNCOV
670
      return point;
!
671
    }
672

673
    point = nextPoint(point);
96×
674
  }
675

676
  return null;
84×
677
}
678

679
/**
680
 * returns whether point has character or not.
681
 *
682
 * @param {Point} point
683
 * @return {Boolean}
684
 */
685
function isCharPoint(point) {
686
  if (!isText(point.node)) {
687
    return false;
688
  }
689

690
  const ch = point.node.nodeValue.charAt(point.offset - 1);
12×
691
  return ch && (ch !== ' ' && ch !== NBSP_CHAR);
423×
692
}
423×
693

423×
694
/**
423×
695
 * @method walkPoint
120×
696
 *
697
 * @param {BoundaryPoint} startPoint
698
 * @param {BoundaryPoint} endPoint
423×
699
 * @param {Function} handler
111×
700
 * @param {Boolean} isSkipInnerOffset
18×
701
 */
702
function walkPoint(startPoint, endPoint, handler, isSkipInnerOffset) {
Branches [[91, 1]] missed. 93×
703
  let point = startPoint;
93×
704

705
  while (point) {
706
    handler(point);
707

312×
708
    if (isSamePoint(point, endPoint)) {
75×
709
      break;
710
    }
711

237×
712
    const isSkipOffset = isSkipInnerOffset &&
237×
713
                       startPoint.node !== point.node &&
237×
714
                       endPoint.node !== point.node;
237×
715
    point = nextPoint(point, isSkipOffset);
132×
716
  }
132×
717
}
718

237×
719
/**
90×
720
 * @method makeOffsetPath
18×
721
 *
722
 * return offsetPath(array of offset) from ancestor
90×
723
 *
18×
724
 * @param {Node} ancestor - ancestor node
18×
725
 * @param {Node} node
726
 */
727
function makeOffsetPath(ancestor, node) {
219×
728
  const ancestors = listAncestor(node, func.eq(ancestor));
729
  return ancestors.map(position).reverse();
730
}
731

732
/**
733
 * @method fromOffsetPath
734
 *
735
 * return element from offsetPath(array of offset)
736
 *
737
 * @param {Node} ancestor - ancestor node
738
 * @param {array} offsets - offsetPath
739
 */
740
function fromOffsetPath(ancestor, offsets) {
741
  let current = ancestor;
742
  for (let i = 0, len = offsets.length; i < len; i++) {
12×
743
    if (current.childNodes.length <= offsets[i]) {
744
      current = current.childNodes[current.childNodes.length - 1];
219×
745
    } else {
Branches [[97, 0]] missed. 219×
UNCOV
746
      current = current.childNodes[offsets[i]];
!
747
    }
748
  }
219×
749
  return current;
81×
750
}
751

138×
752
/**
204×
753
 * @method splitNode
138×
754
 *
755
 * split element or #text
204×
756
 *
757
 * @param {BoundaryPoint} point
758
 * @param {Object} [options]
759
 * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
760
 * @param {Boolean} [options.isNotSplitEdgePoint] - default: false
761
 * @param {Boolean} [options.isDiscardEmptySplits] - default: false
762
 * @return {Node} right node of boundaryPoint
763
 */
764
function splitNode(point, options) {
765
  let isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML;
766
  const isNotSplitEdgePoint = options && options.isNotSplitEdgePoint;
767
  const isDiscardEmptySplits = options && options.isDiscardEmptySplits;
768

12×
769
  if (isDiscardEmptySplits) {
770
    isSkipPaddingBlankHTML = true;
771
  }
772

129×
773
  // edge case
129×
774
  if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) {
129×
775
    if (isLeftEdgePoint(point)) {
129×
776
      return point.node;
129×
777
    } else if (isRightEdgePoint(point)) {
117×
778
      return point.node.nextSibling;
117×
779
    }
780
  }
781

12×
782
  // split #text
12×
783
  if (isText(point.node)) {
784
    return point.node.splitText(point.offset);
785
  } else {
129×
786
    const childNode = point.node.childNodes[point.offset];
787
    const clone = insertAfter(point.node.cloneNode(false), point.node);
788
    appendChildNodes(clone, listNext(childNode));
789

790
    if (!isSkipPaddingBlankHTML) {
129×
791
      paddingBlankHTML(point.node);
27×
792
      paddingBlankHTML(clone);
793
    }
129×
794

795
    if (isDiscardEmptySplits) {
796
      if (isEmpty(point.node)) {
797
        remove(point.node);
798
      }
12×
799
      if (isEmpty(clone)) {
39×
800
        remove(clone);
801
        return point.node.nextSibling;
12×
802
      }
24×
803
    }
804

805
    return clone;
806
  }
807
}
808

809
/**
810
 * @method splitTree
811
 *
812
 * split tree by point
12×
813
 *
174×
814
 * @param {Node} root - split root
3×
815
 * @param {BoundaryPoint} point
816
 * @param {Object} [options]
Branches [[109, 0]] missed. 171×
UNCOV
817
 * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
!
818
 * @param {Boolean} [options.isNotSplitEdgePoint] - default: false
819
 * @return {Node} right node of boundaryPoint
171×
820
 */
171×
821
function splitTree(root, point, options) {
81×
822
  // ex) [#text, <span>, <p>]
81×
UNCOV
823
  const ancestors = listAncestor(point.node, func.eq(root));
!
824

825
  if (!ancestors.length) {
81×
UNCOV
826
    return null;
!
827
  } else if (ancestors.length === 1) {
828
    return splitNode(point, options);
829
  }
171×
830

831
  return ancestors.reduce(function(node, parent) {
832
    if (node === point.node) {
833
      node = splitNode(point, options);
834
    }
835

836
    return splitNode({
837
      node: parent,
12×
UNCOV
838
      offset: node ? position(node) : nodeLength(parent),
!
UNCOV
839
    }, options);
Branches [[111, 0], [111, 1], [112, 0], [112, 1]] missed. !
840
  });
!
841
}
842

!
UNCOV
843
/**
!
844
 * split point
!
845
 *
846
 * @param {Point} point
847
 * @param {Boolean} isInline
848
 * @return {Object}
849
 */
850
function splitPoint(point, isInline) {
851
  // find splitRoot, container
852
  //  - inline: splitRoot is a child of paragraph
853
  //  - block: splitRoot is a child of bodyContainer
854
  const pred = isInline ? isPara : isBodyContainer;
855
  const ancestors = listAncestor(point.node, pred);
856
  const topAncestor = lists.last(ancestors) || point.node;
12×
857

Branches [[113, 0]] missed. 12×
UNCOV
858
  let splitRoot, container;
!
859
  if (pred(topAncestor)) {
860
    splitRoot = ancestors[ancestors.length - 2];
12×
861
    container = topAncestor;
Branches [[114, 0]] missed. 12×
UNCOV
862
  } else {
!
863
    splitRoot = topAncestor;
864
    container = splitRoot.parentNode;
12×
865
  }
12×
866

12×
867
  // if splitRoot is exists, split with splitTree
12×
868
  let pivot = splitRoot && splitTree(splitRoot, point, {
869
    isSkipPaddingBlankHTML: isInline,
6×
870
    isNotSplitEdgePoint: isInline,
871
  });
872

873
  // if container is point.node, find pivot with point.offset
874
  if (!pivot && container === point.node) {
12×
875
    pivot = point.node.childNodes[point.offset];
627×
876
  }
Branches [[116, 0]] missed. 627×
UNCOV
877

!
878
  return {
879
    rightNode: pivot,
627×
880
    container: container,
881
  };
882
}
883

884
function create(nodeName) {
885
  return document.createElement(nodeName);
886
}
887

888
function createText(text) {
889
  return document.createTextNode(text);
12×
890
}
621×
891

Branches [[117, 0]] missed. 621×
UNCOV
892
/**
!
UNCOV
893
 * @method remove
!
894
 *
!
895
 * remove node, (isRemoveChild: remove child or not)
Branches [[118, 0], [118, 1]] missed. !
896
 *
897
 * @param {Node} node
!
UNCOV
898
 * @param {Boolean} isRemoveChild
Branches [[119, 0], [119, 1], [120, 0], [120, 1]] missed. !
899
 */
900
function remove(node, isRemoveChild) {
!
901
  if (!node || !node.parentNode) { return; }
902
  if (node.removeNode) { return node.removeNode(isRemoveChild); }
621×
903

904
  const parent = node.parentNode;
12×
905
  if (!isRemoveChild) {
4×
906
    const nodes = [];
4×
907
    for (let i = 0, len = node.childNodes.length; i < len; i++) {
4×
908
      nodes.push(node.childNodes[i]);
4×
909
    }
910

911
    for (let i = 0, len = nodes.length; i < len; i++) {
912
      parent.insertBefore(nodes[i], node);
913
    }
12×
914
  }
3,093×
915

7,404×
916
  parent.removeChild(node);
917
}
918

12×
919
/**
60×
920
 * @method removeWhile
144×
921
 *
922
 * @param {Node} node
923
 * @param {Function} pred
924
 */
925
function removeWhile(node, pred) {
926
  while (node) {
927
    if (isEditable(node) || !pred(node)) {
928
      break;
929
    }
930

931
    const parent = node.parentNode;
12×
932
    remove(node);
12×
933
    node = parent;
934
  }
6×
935
}
936

937
/**
938
 * @method replace
939
 *
940
 * replace node with provided nodeName
941
 *
942
 * @param {Node} node
943
 * @param {String} nodeName
944
 * @return {Node} - new node
945
 */
946
function replace(node, nodeName) {
947
  if (node.nodeName.toUpperCase() === nodeName.toUpperCase()) {
948
    return node;
949
  }
950

951
  const newNode = create(nodeName);
952

953
  if (node.style.cssText) {
954
    newNode.style.cssText = node.style.cssText;
955
  }
956

957
  appendChildNodes(newNode, lists.from(node.childNodes));
958
  insertAfter(newNode, node);
959
  remove(node);
960

961
  return newNode;
962
}
963

964
const isTextarea = makePredByNodeName('TEXTAREA');
965

966
/**
967
 * @param {jQuery} $node
968
 * @param {Boolean} [stripLinebreaks] - default: false
969
 */
970
function value($node, stripLinebreaks) {
971
  const val = isTextarea($node[0]) ? $node.val() : $node.html();
972
  if (stripLinebreaks) {
973
    return val.replace(/[\n\r]/g, '');
974
  }
975
  return val;
976
}
977

978
/**
979
 * @method html
980
 *
981
 * get the HTML contents of node
982
 *
983
 * @param {jQuery} $node
984
 * @param {Boolean} [isNewlineOnBlock]
985
 */
986
function html($node, isNewlineOnBlock) {
987
  let markup = value($node);
988

989
  if (isNewlineOnBlock) {
990
    const regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g;
991
    markup = markup.replace(regexTag, function(match, endSlash, name) {
992
      name = name.toUpperCase();
993
      const isEndOfInlineContainer = /^DIV|^TD|^TH|^P|^LI|^H[1-7]/.test(name) &&
994
                                   !!endSlash;
995
      const isBlockNode = /^BLOCKQUOTE|^TABLE|^TBODY|^TR|^HR|^UL|^OL/.test(name);
996

997
      return match + ((isEndOfInlineContainer || isBlockNode) ? '\n' : '');
998
    });
999
    markup = markup.trim();
1000
  }
1001

1002
  return markup;
1003
}
1004

1005
function posFromPlaceholder(placeholder) {
1006
  const $placeholder = $(placeholder);
1007
  const pos = $placeholder.offset();
1008
  const height = $placeholder.outerHeight(true); // include margin
1009

1010
  return {
1011
    left: pos.left,
1012
    top: pos.top + height,
1013
  };
1014
}
1015

1016
function attachEvents($node, events) {
1017
  Object.keys(events).forEach(function(key) {
1018
    $node.on(key, events[key]);
1019
  });
1020
}
1021

1022
function detachEvents($node, events) {
1023
  Object.keys(events).forEach(function(key) {
1024
    $node.off(key, events[key]);
1025
  });
1026
}
1027

1028
/**
1029
 * @method isCustomStyleTag
1030
 *
1031
 * assert if a node contains a "note-styletag" class,
1032
 * which implies that's a custom-made style tag node
1033
 *
1034
 * @param {Node} an HTML DOM node
1035
 */
1036
function isCustomStyleTag(node) {
1037
  return node && !isText(node) && lists.contains(node.classList, 'note-styletag');
1038
}
1039

1040
export default {
1041
  /** @property {String} NBSP_CHAR */
1042
  NBSP_CHAR,
1043
  /** @property {String} ZERO_WIDTH_NBSP_CHAR */
1044
  ZERO_WIDTH_NBSP_CHAR,
1045
  /** @property {String} blank */
1046
  blank: blankHTML,
1047
  /** @property {String} emptyPara */
1048
  emptyPara: `<p>${blankHTML}</p>`,
1049
  makePredByNodeName,
1050
  isEditable,
1051
  isControlSizing,
1052
  isText,
1053
  isElement,
1054
  isVoid,
1055
  isPara,
1056
  isPurePara,
1057
  isHeading,
1058
  isInline,
1059
  isBlock: func.not(isInline),
1060
  isBodyInline,
1061
  isBody,
1062
  isParaInline,
1063
  isPre,
1064
  isList,
1065
  isTable,
1066
  isData,
1067
  isCell,
1068
  isBlockquote,
1069
  isBodyContainer,
1070
  isAnchor,
1071
  isDiv: makePredByNodeName('DIV'),
1072
  isLi,
1073
  isBR: makePredByNodeName('BR'),
1074
  isSpan: makePredByNodeName('SPAN'),
1075
  isB: makePredByNodeName('B'),
1076
  isU: makePredByNodeName('U'),
1077
  isS: makePredByNodeName('S'),
1078
  isI: makePredByNodeName('I'),
1079
  isImg: makePredByNodeName('IMG'),
1080
  isTextarea,
1081
  deepestChildIsEmpty,
1082
  isEmpty,
1083
  isEmptyAnchor: func.and(isAnchor, isEmpty),
1084
  isClosestSibling,
1085
  withClosestSiblings,
1086
  nodeLength,
1087
  isLeftEdgePoint,
1088
  isRightEdgePoint,
1089
  isEdgePoint,
1090
  isLeftEdgeOf,
1091
  isRightEdgeOf,
1092
  isLeftEdgePointOf,
1093
  isRightEdgePointOf,
1094
  prevPoint,
1095
  nextPoint,
1096
  isSamePoint,
1097
  isVisiblePoint,
1098
  prevPointUntil,
1099
  nextPointUntil,
1100
  isCharPoint,
1101
  walkPoint,
1102
  ancestor,
1103
  singleChildAncestor,
1104
  listAncestor,
1105
  lastAncestor,
1106
  listNext,
1107
  listPrev,
1108
  listDescendant,
1109
  commonAncestor,
1110
  wrap,
1111
  insertAfter,
1112
  appendChildNodes,
1113
  position,
1114
  hasChildren,
1115
  makeOffsetPath,
1116
  fromOffsetPath,
1117
  splitTree,
1118
  splitPoint,
1119
  create,
1120
  createText,
1121
  remove,
1122
  removeWhile,
1123
  replace,
1124
  html,
1125
  value,
1126
  posFromPlaceholder,
1127
  attachEvents,
1128
  detachEvents,
1129
  isCustomStyleTag,
1130
};
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2021 Coveralls, LLC