lakelib 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/lake.css CHANGED
@@ -32,9 +32,11 @@
32
32
  --input-outline: 2px solid #69b1ff;
33
33
  }
34
34
 
35
+ .lake-container-wrapper {
36
+ position: relative;
37
+ }
35
38
  .lake-container {
36
39
  box-sizing: content-box;
37
- position: relative;
38
40
  font-family: var(--font-family);
39
41
  font-size: 16px;
40
42
  font-weight: normal;
@@ -73,6 +75,23 @@
73
75
  color: var(--link-hover-color);
74
76
  text-decoration: underline;
75
77
  }
78
+ .lake-drop-indication {
79
+ position: absolute;
80
+ height: 2px;
81
+ background-color: #1677ff;
82
+ z-index: 1;
83
+ pointer-events: none;
84
+ display: none;
85
+ }
86
+ .lake-drop-indication svg {
87
+ position: absolute;
88
+ top: -7.5px;
89
+ left: -10px;
90
+ width: 16px;
91
+ height: 16px;
92
+ fill: #1677ff;
93
+ pointer-events: none;
94
+ }
76
95
 
77
96
  .lake-container strong {
78
97
  font-weight: 600;
@@ -862,7 +881,7 @@ lake-box[name="image"] .lake-box-selected .lake-image-error {
862
881
 
863
882
  /* code block */
864
883
  lake-box[name="codeBlock"] {
865
- margin: 16px 0;
884
+ margin-bottom: 16px;
866
885
  }
867
886
  lake-box[name="codeBlock"] .lake-box-focused .lake-code-block,
868
887
  lake-box[name="codeBlock"] .lake-box-activated .lake-code-block {
package/lib/lake.js CHANGED
@@ -446,7 +446,7 @@ function debug(...data) {
446
446
  }
447
447
  }
448
448
 
449
- // Is a key-value object for storing all events.
449
+ // A key-value object for storing all events.
450
450
  // value is an array which include types and listeners.
451
451
  const eventData = {};
452
452
  let lastNodeId = 0;
@@ -733,6 +733,17 @@ class Nodes {
733
733
  closestContainer() {
734
734
  return this.closest('div[contenteditable="true"]');
735
735
  }
736
+ // Traverses the first node and its parents until it finds an element which can scroll.
737
+ closestScroller() {
738
+ let parent = this.eq(0);
739
+ while (parent.length > 0 && parent.isElement) {
740
+ if (['scroll', 'auto'].indexOf(parent.computedCSS('overflow-y')) >= 0) {
741
+ return parent;
742
+ }
743
+ parent = parent.parent();
744
+ }
745
+ return new Nodes();
746
+ }
736
747
  // Returns the parent of the first node.
737
748
  parent() {
738
749
  const node = this.get(0);
@@ -1299,6 +1310,59 @@ class Range {
1299
1310
  get() {
1300
1311
  return this.range;
1301
1312
  }
1313
+ // Returns the size and position of the range.
1314
+ getRect() {
1315
+ const range = this.clone();
1316
+ let rect;
1317
+ let x;
1318
+ let width;
1319
+ if (range.isCollapsed) {
1320
+ let reference = 'left';
1321
+ if (range.startNode.isElement) {
1322
+ const children = range.startNode.children();
1323
+ if (children.length === 0) {
1324
+ range.selectNode(range.startNode);
1325
+ }
1326
+ else if (range.startOffset < children.length) {
1327
+ range.setEnd(range.startNode, range.startOffset + 1);
1328
+ }
1329
+ else {
1330
+ range.setStart(range.startNode, range.startOffset - 1);
1331
+ reference = 'right';
1332
+ }
1333
+ }
1334
+ else {
1335
+ const text = range.startNode.text();
1336
+ if (range.startOffset < text.length) {
1337
+ range.setEnd(range.startNode, range.startOffset + 1);
1338
+ }
1339
+ else {
1340
+ range.setStart(range.startNode, range.startOffset - 1);
1341
+ reference = 'right';
1342
+ }
1343
+ }
1344
+ rect = range.get().getBoundingClientRect();
1345
+ if (reference === 'left') {
1346
+ x = rect.x;
1347
+ }
1348
+ else {
1349
+ x = rect.right;
1350
+ }
1351
+ width = 1;
1352
+ }
1353
+ else {
1354
+ rect = range.get().getBoundingClientRect();
1355
+ x = rect.x;
1356
+ width = rect.width;
1357
+ }
1358
+ const height = rect.height;
1359
+ return DOMRect.fromRect({
1360
+ x,
1361
+ y: rect.y,
1362
+ width: width > 0 ? width : 1,
1363
+ height: height > 0 ? height : 1,
1364
+ });
1365
+ }
1302
1366
  // Returns −1 if the point is before the range, 0 if the point is in the range, and 1 if the point is after the range.
1303
1367
  comparePoint(node, offset) {
1304
1368
  return this.range.comparePoint(node.get(0), offset);
@@ -2797,13 +2861,13 @@ const boxes = new Map();
2797
2861
 
2798
2862
  const editors = new Map();
2799
2863
 
2800
- // Is a key-value object for storing data about box.
2864
+ // A key-value object for storing data about box.
2801
2865
  const boxData = {};
2802
- // Is a key-value object for storing all effects.
2866
+ // A key-value object for storing all effects.
2803
2867
  const effectData = {};
2804
2868
  const framework = safeTemplate `
2805
2869
  <span class="lake-box-strip"><br /></span>
2806
- <div class="lake-box-container" contenteditable="false"></div>
2870
+ <div class="lake-box-container" contenteditable="false" draggable="true"></div>
2807
2871
  <span class="lake-box-strip"><br /></span>
2808
2872
  `;
2809
2873
  class Box {
@@ -2853,10 +2917,6 @@ class Box {
2853
2917
  container.off('mouseleave');
2854
2918
  container.off('click');
2855
2919
  }
2856
- // fix: should not activate box when clicking box
2857
- container.on('mousedown', event => {
2858
- event.preventDefault();
2859
- });
2860
2920
  container.on('mouseenter', () => {
2861
2921
  if (container.hasClass('lake-box-selected') ||
2862
2922
  container.hasClass('lake-box-focused') ||
@@ -4613,7 +4673,7 @@ class Dropdown {
4613
4673
  }
4614
4674
  }
4615
4675
 
4616
- var version = "0.1.7";
4676
+ var version = "0.1.8";
4617
4677
 
4618
4678
  // Inserts a box into the specified range.
4619
4679
  function insertBox(range, boxName, boxValue) {
@@ -5231,6 +5291,27 @@ class Editor {
5231
5291
  this.isComposing = false;
5232
5292
  this.event = new EventEmitter();
5233
5293
  this.box = Editor.box;
5294
+ this.copyListener = event => {
5295
+ const range = this.selection.range;
5296
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5297
+ return;
5298
+ }
5299
+ this.event.emit('copy', event);
5300
+ };
5301
+ this.cutListener = event => {
5302
+ const range = this.selection.range;
5303
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5304
+ return;
5305
+ }
5306
+ this.event.emit('cut', event);
5307
+ };
5308
+ this.pasteListener = event => {
5309
+ const range = this.selection.range;
5310
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5311
+ return;
5312
+ }
5313
+ this.event.emit('paste', event);
5314
+ };
5234
5315
  this.beforeunloadListener = () => {
5235
5316
  this.history.save();
5236
5317
  };
@@ -5345,6 +5426,7 @@ class Editor {
5345
5426
  this.rectifyContent();
5346
5427
  this.emitStateChangeEvent();
5347
5428
  this.togglePlaceholderClass(value);
5429
+ this.scrollToCaret();
5348
5430
  this.event.emit('change', value);
5349
5431
  };
5350
5432
  if (!config.root) {
@@ -5560,6 +5642,66 @@ class Editor {
5560
5642
  blur() {
5561
5643
  this.container.blur();
5562
5644
  }
5645
+ // Scrolls to the caret or the range of the selection.
5646
+ scrollToCaret() {
5647
+ // Creates an artificial caret that is the same size as the caret at the current caret position.
5648
+ const rangeRect = this.selection.range.getRect();
5649
+ const containerRect = this.container.get(0).getBoundingClientRect();
5650
+ const artificialCaret = query('<div class="lake-artificial-caret" />');
5651
+ const left = rangeRect.x - containerRect.x;
5652
+ const top = rangeRect.y - containerRect.y;
5653
+ artificialCaret.css({
5654
+ position: 'absolute',
5655
+ top: `${top}px`,
5656
+ left: `${left}px`,
5657
+ width: `${rangeRect.width}px`,
5658
+ height: `${rangeRect.height}px`,
5659
+ // background: 'red',
5660
+ 'z-index': '-1',
5661
+ });
5662
+ this.overlayContainer.find('.lake-artificial-caret').remove();
5663
+ this.overlayContainer.append(artificialCaret);
5664
+ // Scrolls the artificial caret element into the visible area of the browser window
5665
+ // if it's not already within the visible area of the browser window.
5666
+ // If the element is already within the visible area of the browser window, then no scrolling takes place.
5667
+ let scrollX;
5668
+ let scrollY;
5669
+ let viewportWidth;
5670
+ let viewportHeight;
5671
+ const viewport = this.container.closestScroller();
5672
+ if (viewport.length > 0) {
5673
+ const nativeViewport = viewport.get(0);
5674
+ const viewportRect = nativeViewport.getBoundingClientRect();
5675
+ scrollX = nativeViewport.scrollLeft;
5676
+ scrollY = nativeViewport.scrollTop;
5677
+ viewportWidth = viewportRect.width;
5678
+ viewportHeight = viewportRect.height;
5679
+ }
5680
+ else {
5681
+ const nativeContainerWrapper = this.containerWrapper.get(0);
5682
+ scrollX = window.scrollX;
5683
+ scrollY = window.scrollY;
5684
+ viewportWidth = window.innerWidth - nativeContainerWrapper.offsetLeft;
5685
+ viewportHeight = window.innerHeight - nativeContainerWrapper.offsetTop;
5686
+ }
5687
+ let needScroll = false;
5688
+ let alignToTop = true;
5689
+ if (left < scrollX || left > scrollX + viewportWidth) {
5690
+ needScroll = true;
5691
+ }
5692
+ if (top < scrollY) {
5693
+ needScroll = true;
5694
+ alignToTop = true;
5695
+ }
5696
+ else if (top > scrollY + viewportHeight) {
5697
+ needScroll = true;
5698
+ alignToTop = false;
5699
+ }
5700
+ if (needScroll) {
5701
+ artificialCaret.get(0).scrollIntoView(alignToTop);
5702
+ }
5703
+ artificialCaret.remove();
5704
+ }
5563
5705
  // Sets the specified HTML string to the editor area.
5564
5706
  setValue(value) {
5565
5707
  value = normalizeValue(value);
@@ -5619,7 +5761,10 @@ class Editor {
5619
5761
  if (this.toolbar) {
5620
5762
  this.toolbar.render(this);
5621
5763
  }
5764
+ document.addEventListener('copy', this.copyListener);
5622
5765
  if (!this.readonly) {
5766
+ document.addEventListener('cut', this.cutListener);
5767
+ document.addEventListener('paste', this.pasteListener);
5623
5768
  window.addEventListener('beforeunload', this.beforeunloadListener);
5624
5769
  document.addEventListener('selectionchange', this.selectionchangeListener);
5625
5770
  document.addEventListener('click', this.clickListener);
@@ -5635,7 +5780,10 @@ class Editor {
5635
5780
  this.history.event.removeAllListeners();
5636
5781
  this.root.empty();
5637
5782
  this.popupContainer.remove();
5783
+ document.removeEventListener('copy', this.copyListener);
5638
5784
  if (!this.readonly) {
5785
+ document.removeEventListener('cut', this.cutListener);
5786
+ document.removeEventListener('paste', this.pasteListener);
5639
5787
  window.removeEventListener('beforeunload', this.beforeunloadListener);
5640
5788
  document.removeEventListener('selectionchange', this.selectionchangeListener);
5641
5789
  document.removeEventListener('click', this.clickListener);
@@ -6764,11 +6912,18 @@ function openFullScreen(box) {
6764
6912
  }
6765
6913
  return placeholderSrc;
6766
6914
  });
6915
+ let savedRange;
6767
6916
  lightbox.on('openingAnimationEnd', () => {
6917
+ savedRange = editor.selection.range;
6768
6918
  box.event.emit('openfullscreen');
6769
6919
  });
6770
6920
  lightbox.on('destroy', () => {
6771
6921
  window.setTimeout(() => {
6922
+ if (savedRange) {
6923
+ // fix(image): lose focus when zooming in the iOS
6924
+ editor.selection.range = savedRange;
6925
+ editor.selection.addRangeToNativeSelection();
6926
+ }
6772
6927
  box.event.emit('closefullscreen');
6773
6928
  }, 0);
6774
6929
  });
@@ -6980,7 +7135,7 @@ const imageBox = {
6980
7135
  }
6981
7136
  }
6982
7137
  if (container.first().length === 0) {
6983
- // The code below is for unit testing because some test cases need to
7138
+ // The following code is for unit testing because some test cases need to
6984
7139
  // select the content of the box before it is completely loaded.
6985
7140
  // Example:
6986
7141
  // range.setStart(box.getContainer(), 1);
@@ -7219,10 +7374,7 @@ const codeBlockBox = {
7219
7374
  };
7220
7375
 
7221
7376
  var copy = (editor) => {
7222
- if (editor.readonly) {
7223
- return;
7224
- }
7225
- editor.container.on('copy', event => {
7377
+ editor.event.on('copy', event => {
7226
7378
  const range = editor.selection.range;
7227
7379
  if (range.isInsideBox) {
7228
7380
  return;
@@ -7249,7 +7401,7 @@ var cut = (editor) => {
7249
7401
  if (editor.readonly) {
7250
7402
  return;
7251
7403
  }
7252
- editor.container.on('cut', event => {
7404
+ editor.event.on('cut', event => {
7253
7405
  const range = editor.selection.range;
7254
7406
  if (range.isInsideBox) {
7255
7407
  return;
@@ -7433,7 +7585,7 @@ var paste = (editor) => {
7433
7585
  if (editor.readonly) {
7434
7586
  return;
7435
7587
  }
7436
- editor.container.on('paste', event => {
7588
+ editor.event.on('paste', event => {
7437
7589
  const { requestTypes } = editor.config.image;
7438
7590
  const range = editor.selection.range;
7439
7591
  if (range.isInsideBox) {
@@ -7478,6 +7630,154 @@ var paste = (editor) => {
7478
7630
  });
7479
7631
  };
7480
7632
 
7633
+ var drop = (editor) => {
7634
+ if (editor.readonly) {
7635
+ return;
7636
+ }
7637
+ let draggedNode = null;
7638
+ let dropIndication = null;
7639
+ let targetBlock = null;
7640
+ let dropPosition = 'bottom';
7641
+ // The dragstart event is fired when the user starts dragging an element or text selection.
7642
+ editor.container.on('dragstart', event => {
7643
+ draggedNode = null;
7644
+ const dragEvent = event;
7645
+ const dataTransfer = dragEvent.dataTransfer;
7646
+ if (!dataTransfer) {
7647
+ return;
7648
+ }
7649
+ dataTransfer.effectAllowed = 'move';
7650
+ // set the dragged node
7651
+ const targetNode = query(dragEvent.target);
7652
+ const boxNode = targetNode.closest('lake-box');
7653
+ if (boxNode.length === 0) {
7654
+ dragEvent.preventDefault();
7655
+ return;
7656
+ }
7657
+ const box = new Box(boxNode);
7658
+ if (box.type === 'inline') {
7659
+ dragEvent.preventDefault();
7660
+ return;
7661
+ }
7662
+ draggedNode = boxNode;
7663
+ // prepare an indication rod
7664
+ dropIndication = query(safeTemplate `
7665
+ <div class="lake-drop-indication">
7666
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
7667
+ <path d="M181.66,133.66l-80,80A8,8,0,0,1,88,208V48a8,8,0,0,1,13.66-5.66l80,80A8,8,0,0,1,181.66,133.66Z"></path>
7668
+ </svg>
7669
+ </div>
7670
+ `);
7671
+ editor.overlayContainer.append(dropIndication);
7672
+ });
7673
+ // The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).
7674
+ editor.container.on('dragover', event => {
7675
+ const dragEvent = event;
7676
+ dragEvent.preventDefault();
7677
+ const dataTransfer = dragEvent.dataTransfer;
7678
+ if (!dataTransfer) {
7679
+ return;
7680
+ }
7681
+ dataTransfer.dropEffect = 'move';
7682
+ if (!dropIndication) {
7683
+ return;
7684
+ }
7685
+ const targetNode = query(dragEvent.target);
7686
+ if (targetNode.isContainer) {
7687
+ return;
7688
+ }
7689
+ const targetBoxNode = targetNode.closest('lake-box');
7690
+ if (targetBoxNode.length > 0) {
7691
+ if (targetBoxNode.isBlockBox) {
7692
+ targetBlock = targetBoxNode;
7693
+ }
7694
+ else {
7695
+ targetBlock = targetBoxNode.closestBlock();
7696
+ }
7697
+ }
7698
+ else {
7699
+ targetBlock = targetNode.closestBlock();
7700
+ }
7701
+ const containerRect = editor.container.get(0).getBoundingClientRect();
7702
+ let targetBlcokRect = targetBlock.get(0).getBoundingClientRect();
7703
+ dropPosition = 'bottom';
7704
+ let left = targetBlcokRect.x - containerRect.x;
7705
+ let top = targetBlcokRect.y + targetBlcokRect.height - containerRect.y + (parseInt(targetBlock.computedCSS('margin-bottom'), 10) / 2);
7706
+ if (dragEvent.clientY < targetBlcokRect.y + (targetBlcokRect.height / 2)) {
7707
+ const prevBlock = targetBlock.prev();
7708
+ if (prevBlock.length > 0 && prevBlock.isBlock || prevBlock.isBlockBox) {
7709
+ targetBlock = prevBlock;
7710
+ targetBlcokRect = targetBlock.get(0).getBoundingClientRect();
7711
+ left = targetBlcokRect.x - containerRect.x;
7712
+ top = targetBlcokRect.y + targetBlcokRect.height - containerRect.y + (parseInt(targetBlock.computedCSS('margin-bottom'), 10) / 2);
7713
+ }
7714
+ else {
7715
+ dropPosition = 'top';
7716
+ top = targetBlcokRect.y - containerRect.y - (parseInt(editor.container.computedCSS('padding-top'), 10) / 2);
7717
+ }
7718
+ }
7719
+ dropIndication.css({
7720
+ top: `${top}px`,
7721
+ left: `${left}px`,
7722
+ width: `${targetBlcokRect.width}px`,
7723
+ display: 'block',
7724
+ });
7725
+ });
7726
+ // The dragend event is fired when a drag operation ends (by releasing a mouse button or hitting the escape key).
7727
+ editor.container.on('dragend', () => {
7728
+ if (!dropIndication) {
7729
+ return;
7730
+ }
7731
+ dropIndication.remove();
7732
+ dropIndication = null;
7733
+ });
7734
+ // The drop event is fired when an element or text selection is dropped on a valid drop target.
7735
+ editor.container.on('drop', event => {
7736
+ const dragEvent = event;
7737
+ const dataTransfer = dragEvent.dataTransfer;
7738
+ if (!dataTransfer) {
7739
+ return;
7740
+ }
7741
+ if (!dropIndication) {
7742
+ return;
7743
+ }
7744
+ dropIndication.remove();
7745
+ dropIndication = null;
7746
+ // drop a box
7747
+ if (draggedNode && targetBlock && draggedNode.isBox) {
7748
+ if (draggedNode.get(0) === targetBlock.get(0)) {
7749
+ return;
7750
+ }
7751
+ if (dropPosition === 'bottom' && draggedNode.get(0) === targetBlock.next().get(0)) {
7752
+ return;
7753
+ }
7754
+ dragEvent.preventDefault();
7755
+ const draggedBox = new Box(draggedNode);
7756
+ const range = editor.selection.range;
7757
+ if (targetBlock.isBox) {
7758
+ if (dropPosition === 'top') {
7759
+ range.selectBoxStart(targetBlock);
7760
+ }
7761
+ else {
7762
+ range.selectBoxEnd(targetBlock);
7763
+ }
7764
+ }
7765
+ else {
7766
+ range.selectNodeContents(targetBlock);
7767
+ if (dropPosition === 'top') {
7768
+ range.collapseToStart();
7769
+ }
7770
+ else {
7771
+ range.collapseToEnd();
7772
+ }
7773
+ }
7774
+ editor.insertBox(draggedBox.name, draggedBox.value);
7775
+ draggedNode.remove();
7776
+ editor.history.save();
7777
+ }
7778
+ });
7779
+ };
7780
+
7481
7781
  var undo = (editor) => {
7482
7782
  if (editor.readonly) {
7483
7783
  return;
@@ -8740,12 +9040,10 @@ function splitBlock(editor, block) {
8740
9040
  editor.selection.splitBlock();
8741
9041
  block = range.getBlocks()[0];
8742
9042
  if (!block) {
8743
- editor.history.save();
8744
9043
  return;
8745
9044
  }
8746
9045
  if (endText === '' && (block.isHeading || block.name === 'blockquote')) {
8747
9046
  editor.selection.setBlocks('<p />');
8748
- editor.history.save();
8749
9047
  return;
8750
9048
  }
8751
9049
  if (block.isList && block.attr('type') === 'checklist') {
@@ -9308,6 +9606,7 @@ Editor.box.add(codeBlockBox);
9308
9606
  Editor.plugin.add(copy);
9309
9607
  Editor.plugin.add(cut);
9310
9608
  Editor.plugin.add(paste);
9609
+ Editor.plugin.add(drop);
9311
9610
  Editor.plugin.add(undo);
9312
9611
  Editor.plugin.add(redo);
9313
9612
  Editor.plugin.add(selectAll);