lakelib 0.1.7 → 0.1.9

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.js CHANGED
@@ -37,8 +37,12 @@ var checkCircle = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height
37
37
 
38
38
  var warningCircle = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z\"></path></svg>";
39
39
 
40
+ var file$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z\"></path></svg>";
41
+
40
42
  var open = "<svg width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z\" fill=\"#000000\"/></svg>";
41
43
 
44
+ var download = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M224,144v64a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V144a8,8,0,0,1,16,0v56H208V144a8,8,0,0,1,16,0Zm-101.66,5.66a8,8,0,0,0,11.32,0l40-40a8,8,0,0,0-11.32-11.32L136,124.69V32a8,8,0,0,0-16,0v92.69L93.66,98.34a8,8,0,0,0-11.32,11.32Z\"></path></svg>";
45
+
42
46
  var copy$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"></path></svg>";
43
47
 
44
48
  var remove = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z\"></path></svg>";
@@ -133,6 +137,8 @@ var hr$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\"
133
137
 
134
138
  var image$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z\"></path></svg>";
135
139
 
140
+ var attachment = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M209.66,122.34a8,8,0,0,1,0,11.32l-82.05,82a56,56,0,0,1-79.2-79.21L147.67,35.73a40,40,0,1,1,56.61,56.55L105,193A24,24,0,1,1,71,159L154.3,74.38A8,8,0,1,1,165.7,85.6L82.39,170.31a8,8,0,1,0,11.27,11.36L192.93,81A24,24,0,1,0,159,47L59.76,147.68a40,40,0,1,0,56.53,56.62l82.06-82A8,8,0,0,1,209.66,122.34Z\"></path></svg>";
141
+
136
142
  var codeBlock$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M58.34,101.66l-32-32a8,8,0,0,1,0-11.32l32-32A8,8,0,0,1,69.66,37.66L43.31,64,69.66,90.34a8,8,0,0,1-11.32,11.32Zm40,0a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0,0-11.32l-32-32A8,8,0,0,0,98.34,37.66L124.69,64,98.34,90.34A8,8,0,0,0,98.34,101.66ZM200,40H176a8,8,0,0,0,0,16h24V200H56V136a8,8,0,0,0-16,0v64a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Z\"></path></svg>";
137
143
 
138
144
  var table = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM40,112H80v32H40Zm56,0H216v32H96ZM216,64V96H40V64ZM40,160H80v32H40Zm176,32H96V160H216v32Z\"></path></svg>";
@@ -160,7 +166,9 @@ const icons = new Map([
160
166
  ['check', check],
161
167
  ['checkCircle', checkCircle],
162
168
  ['warningCircle', warningCircle],
169
+ ['file', file$1],
163
170
  ['open', open],
171
+ ['download', download],
164
172
  ['copy', copy$1],
165
173
  ['remove', remove],
166
174
  ['maximize', maximize],
@@ -208,6 +216,7 @@ const icons = new Map([
208
216
  ['unlink', unlink],
209
217
  ['hr', hr$1],
210
218
  ['image', image$1],
219
+ ['attachment', attachment],
211
220
  ['codeBlock', codeBlock$1],
212
221
  ['table', table],
213
222
  ]);
@@ -324,12 +333,29 @@ function denormalizeValue(value) {
324
333
  // Mac: mod+Z returns ⌘+Z
325
334
  // Windows / Linux: mod+Z returns Ctrl+Z
326
335
  function modifierText(value, userAgent) {
336
+ // for generating i18n files
337
+ if (typeof window === 'undefined') {
338
+ return value;
339
+ }
327
340
  userAgent = userAgent !== null && userAgent !== void 0 ? userAgent : navigator.userAgent;
328
341
  const isMac = userAgent.indexOf('Mac OS X') >= 0;
329
342
  const modText = isMac ? '⌘' : 'Ctrl';
330
343
  return value.replace(/(^|\+|\s)mod(\+|\s|$)/g, `$1${modText}$2`);
331
344
  }
332
345
 
346
+ // Returns a human-readable file size string from a number.
347
+ function fileSize(size) {
348
+ const units = ['KB', 'MB', 'GB'];
349
+ let i = 0;
350
+ size /= 1024;
351
+ while (size > 1024 && i < 2) {
352
+ size /= 1024;
353
+ i++;
354
+ }
355
+ const sizeString = size > 0 ? Math.max(size, 0.1).toFixed(1) : 0;
356
+ return `${sizeString} ${units[i]}`;
357
+ }
358
+
333
359
  // Returns a property value of all CSS properties of an element
334
360
  function getCSS(element, propertyName) {
335
361
  const camelPropertyName = camelCase(propertyName);
@@ -446,7 +472,7 @@ function debug(...data) {
446
472
  }
447
473
  }
448
474
 
449
- // Is a key-value object for storing all events.
475
+ // A key-value object for storing all events.
450
476
  // value is an array which include types and listeners.
451
477
  const eventData = {};
452
478
  let lastNodeId = 0;
@@ -733,6 +759,17 @@ class Nodes {
733
759
  closestContainer() {
734
760
  return this.closest('div[contenteditable="true"]');
735
761
  }
762
+ // Traverses the first node and its parents until it finds an element which can scroll.
763
+ closestScroller() {
764
+ let parent = this.eq(0);
765
+ while (parent.length > 0 && parent.isElement) {
766
+ if (['scroll', 'auto'].indexOf(parent.computedCSS('overflow-y')) >= 0) {
767
+ return parent;
768
+ }
769
+ parent = parent.parent();
770
+ }
771
+ return new Nodes();
772
+ }
736
773
  // Returns the parent of the first node.
737
774
  parent() {
738
775
  const node = this.get(0);
@@ -1299,6 +1336,59 @@ class Range {
1299
1336
  get() {
1300
1337
  return this.range;
1301
1338
  }
1339
+ // Returns the size and position of the range.
1340
+ getRect() {
1341
+ const range = this.clone();
1342
+ let rect;
1343
+ let x;
1344
+ let width;
1345
+ if (range.isCollapsed) {
1346
+ let reference = 'left';
1347
+ if (range.startNode.isElement) {
1348
+ const children = range.startNode.children();
1349
+ if (children.length === 0) {
1350
+ range.selectNode(range.startNode);
1351
+ }
1352
+ else if (range.startOffset < children.length) {
1353
+ range.setEnd(range.startNode, range.startOffset + 1);
1354
+ }
1355
+ else {
1356
+ range.setStart(range.startNode, range.startOffset - 1);
1357
+ reference = 'right';
1358
+ }
1359
+ }
1360
+ else {
1361
+ const text = range.startNode.text();
1362
+ if (range.startOffset < text.length) {
1363
+ range.setEnd(range.startNode, range.startOffset + 1);
1364
+ }
1365
+ else {
1366
+ range.setStart(range.startNode, range.startOffset - 1);
1367
+ reference = 'right';
1368
+ }
1369
+ }
1370
+ rect = range.get().getBoundingClientRect();
1371
+ if (reference === 'left') {
1372
+ x = rect.x;
1373
+ }
1374
+ else {
1375
+ x = rect.right;
1376
+ }
1377
+ width = 1;
1378
+ }
1379
+ else {
1380
+ rect = range.get().getBoundingClientRect();
1381
+ x = rect.x;
1382
+ width = rect.width;
1383
+ }
1384
+ const height = rect.height;
1385
+ return DOMRect.fromRect({
1386
+ x,
1387
+ y: rect.y,
1388
+ width: width > 0 ? width : 1,
1389
+ height: height > 0 ? height : 1,
1390
+ });
1391
+ }
1302
1392
  // 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
1393
  comparePoint(node, offset) {
1304
1394
  return this.range.comparePoint(node.get(0), offset);
@@ -2614,192 +2704,54 @@ function morph(node, otherNode, config = {}) {
2614
2704
  morphNormalizedContent(node.get(0), normalizedContent, ctx);
2615
2705
  }
2616
2706
 
2617
- /**
2618
- The MIT License (MIT)
2619
-
2620
- Copyright (c) 2016-present react-component
2621
-
2622
- Permission is hereby granted, free of charge, to any person obtaining a copy
2623
- of this software and associated documentation files (the "Software"), to deal
2624
- in the Software without restriction, including without limitation the rights
2625
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2626
- copies of the Software, and to permit persons to whom the Software is
2627
- furnished to do so, subject to the following conditions:
2628
-
2629
- The above copyright notice and this permission notice shall be included in
2630
- all copies or substantial portions of the Software.
2631
-
2632
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
2633
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2634
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2635
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2636
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2637
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2638
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2639
-
2640
- Repository: https://github.com/react-component/upload
2641
- */
2642
- function getError(option, xhr) {
2643
- const msg = `Cannot ${option.method} ${option.action} ${xhr.status}'`;
2644
- const err = new Error(msg);
2645
- err.status = xhr.status;
2646
- err.method = option.method;
2647
- err.url = option.action;
2648
- return err;
2649
- }
2650
- function getBody(xhr) {
2651
- const text = xhr.responseText || xhr.response;
2652
- if (!text) {
2653
- return text;
2654
- }
2655
- try {
2656
- return JSON.parse(text);
2657
- }
2658
- catch (e) {
2659
- return text;
2660
- }
2661
- }
2662
- function request(option) {
2663
- const xhr = new XMLHttpRequest();
2664
- if (option.onProgress && xhr.upload) {
2665
- xhr.upload.onprogress = (e) => {
2666
- if (e.total > 0) {
2667
- e.percent = (e.loaded / e.total) * 100;
2668
- }
2669
- if (option.onProgress) {
2670
- option.onProgress(e);
2671
- }
2672
- };
2673
- }
2674
- const formData = new FormData();
2675
- const data = option.data || {};
2676
- Object.keys(data).forEach(key => {
2677
- const value = data[key];
2678
- // support key-value array data
2679
- if (Array.isArray(value)) {
2680
- value.forEach(item => {
2681
- // { list: [ 11, 22 ] }
2682
- // formData.append('list[]', 11);
2683
- formData.append(`${key}[]`, item);
2684
- });
2685
- return;
2686
- }
2687
- formData.append(key, value);
2688
- });
2689
- const filename = option.filename || 'file';
2690
- if (option.file instanceof Blob) {
2691
- formData.append(filename, option.file, option.file.name);
2692
- }
2693
- else {
2694
- formData.append(filename, option.file);
2695
- }
2696
- xhr.onerror = (e) => {
2697
- if (option.onError) {
2698
- option.onError(e);
2699
- }
2700
- };
2701
- xhr.onload = () => {
2702
- // allow success when 2xx status
2703
- // see https://github.com/react-component/upload/issues/34
2704
- if (xhr.status < 200 || xhr.status >= 300) {
2705
- if (!option.onError) {
2706
- return;
2707
- }
2708
- return option.onError(getError(option, xhr), getBody(xhr));
2709
- }
2710
- if (!option.onSuccess) {
2711
- return;
2712
- }
2713
- return option.onSuccess(getBody(xhr), xhr);
2707
+ // Returns an object that indicates the specified node's position relative to the viewport.
2708
+ function nodeAndView(node) {
2709
+ const nativeNode = node.get(0);
2710
+ const rect = nativeNode.getBoundingClientRect();
2711
+ let left = rect.left;
2712
+ let right = rect.right;
2713
+ let top = rect.top;
2714
+ let bottom = rect.bottom;
2715
+ let viewportWidth = window.innerWidth;
2716
+ let viewportHeight = window.innerHeight;
2717
+ const container = node.closestContainer();
2718
+ if (container.length > 0) {
2719
+ const viewport = container.closestScroller();
2720
+ if (viewport.length > 0) {
2721
+ const containerWrapper = container.parent();
2722
+ if (containerWrapper.length > 0) {
2723
+ const nativeContainerWrapper = containerWrapper.get(0);
2724
+ const offsetLeft = nativeContainerWrapper.offsetLeft - window.scrollX;
2725
+ const offsetTop = nativeContainerWrapper.offsetTop - window.scrollY;
2726
+ left -= offsetLeft;
2727
+ right -= offsetLeft;
2728
+ top -= offsetTop;
2729
+ bottom -= offsetTop;
2730
+ }
2731
+ const nativeViewport = viewport.get(0);
2732
+ const viewportRect = nativeViewport.getBoundingClientRect();
2733
+ viewportWidth = viewportRect.width;
2734
+ viewportHeight = viewportRect.height;
2735
+ }
2736
+ }
2737
+ const position = {
2738
+ left,
2739
+ right: viewportWidth - right,
2740
+ top,
2741
+ bottom: viewportHeight - bottom,
2714
2742
  };
2715
- xhr.open(option.method, option.action, true);
2716
- // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
2717
- if (option.withCredentials && 'withCredentials' in xhr) {
2718
- xhr.withCredentials = true;
2719
- }
2720
- const headers = option.headers || {};
2721
- // when set headers['X-Requested-With'] = null , can close default XHR header
2722
- // see https://github.com/react-component/upload/issues/33
2723
- if (headers['X-Requested-With'] !== null) {
2724
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2725
- }
2726
- Object.keys(headers).forEach(h => {
2727
- if (headers[h] !== null) {
2728
- xhr.setRequestHeader(h, headers[h]);
2729
- }
2730
- });
2731
- xhr.send(formData);
2732
- return xhr;
2743
+ return position;
2733
2744
  }
2734
2745
 
2735
- // String
2736
-
2737
- var index = /*#__PURE__*/Object.freeze({
2738
- __proto__: null,
2739
- appendDeepest: appendDeepest,
2740
- camelCase: camelCase,
2741
- changeTagName: changeTagName,
2742
- debug: debug,
2743
- denormalizeValue: denormalizeValue,
2744
- encode: encode,
2745
- fixNumberedList: fixNumberedList,
2746
- getCSS: getCSS,
2747
- getDeepest: getDeepest,
2748
- inString: inString,
2749
- mergeNodes: mergeNodes,
2750
- modifierText: modifierText,
2751
- morph: morph,
2752
- normalizeValue: normalizeValue,
2753
- parseStyle: parseStyle,
2754
- query: query,
2755
- removeBr: removeBr,
2756
- removeZWS: removeZWS,
2757
- request: request,
2758
- safeTemplate: safeTemplate,
2759
- setBlockIndent: setBlockIndent,
2760
- splitNodes: splitNodes,
2761
- template: template,
2762
- toHex: toHex,
2763
- toNodeList: toNodeList,
2764
- wrapNodeList: wrapNodeList
2765
- });
2766
-
2767
- class Fragment {
2768
- constructor(fragment) {
2769
- this.fragment = fragment !== null && fragment !== void 0 ? fragment : document.createDocumentFragment();
2770
- }
2771
- // Returns the descendants of the fragment which are selected by the specified CSS selector.
2772
- find(selector) {
2773
- const nodeList = [];
2774
- let child = new Nodes(this.fragment.firstChild);
2775
- while (child.length > 0) {
2776
- if (child.matches(selector)) {
2777
- nodeList.push(child.get(0));
2778
- }
2779
- else if (child.isElement) {
2780
- child.find(selector).each(node => {
2781
- nodeList.push(node);
2782
- });
2783
- }
2784
- child = child.next();
2785
- }
2786
- return new Nodes(nodeList);
2787
- }
2788
- // Inserts the specified node as the last child.
2789
- append(node) {
2790
- node.each(nativeNode => {
2791
- this.fragment.appendChild(nativeNode);
2792
- });
2793
- }
2794
- }
2746
+ const boxInstances = new Map();
2795
2747
 
2796
2748
  const boxes = new Map();
2797
2749
 
2798
2750
  const editors = new Map();
2799
2751
 
2800
- // Is a key-value object for storing data about box.
2752
+ // A key-value object for storing data about box.
2801
2753
  const boxData = {};
2802
- // Is a key-value object for storing all effects.
2754
+ // A key-value object for storing all effects.
2803
2755
  const effectData = {};
2804
2756
  const framework = safeTemplate `
2805
2757
  <span class="lake-box-strip"><br /></span>
@@ -2853,10 +2805,6 @@ class Box {
2853
2805
  container.off('mouseleave');
2854
2806
  container.off('click');
2855
2807
  }
2856
- // fix: should not activate box when clicking box
2857
- container.on('mousedown', event => {
2858
- event.preventDefault();
2859
- });
2860
2808
  container.on('mouseenter', () => {
2861
2809
  if (container.hasClass('lake-box-selected') ||
2862
2810
  container.hasClass('lake-box-focused') ||
@@ -2872,6 +2820,9 @@ class Box {
2872
2820
  debug(`Box '${this.name}' (id = ${this.node.id}) value:`);
2873
2821
  debug(this.value);
2874
2822
  });
2823
+ if (this.type === 'block') {
2824
+ container.attr('draggable', 'true');
2825
+ }
2875
2826
  }
2876
2827
  // Returns the type of the box.
2877
2828
  get type() {
@@ -2951,6 +2902,7 @@ class Box {
2951
2902
  }
2952
2903
  // Destroys a rendered box.
2953
2904
  unmount() {
2905
+ this.event.emit('blur');
2954
2906
  for (const cleanup of effectData[this.node.id].cleanup) {
2955
2907
  cleanup();
2956
2908
  }
@@ -2974,6 +2926,212 @@ class Box {
2974
2926
  }
2975
2927
  }
2976
2928
 
2929
+ // Returns an already generated box instance or generates a new instance if it does not exist.
2930
+ function getBox(boxNode) {
2931
+ if (typeof boxNode === 'string') {
2932
+ return new Box(boxNode);
2933
+ }
2934
+ boxNode = query(boxNode);
2935
+ const container = boxNode.closestContainer();
2936
+ if (container.length === 0) {
2937
+ return new Box(boxNode);
2938
+ }
2939
+ let instanceMap = boxInstances.get(container.id);
2940
+ if (!instanceMap) {
2941
+ instanceMap = new Map();
2942
+ boxInstances.set(container.id, instanceMap);
2943
+ }
2944
+ let box = instanceMap.get(boxNode.id);
2945
+ if (box) {
2946
+ return box;
2947
+ }
2948
+ box = new Box(boxNode);
2949
+ instanceMap.set(box.node.id, box);
2950
+ return box;
2951
+ }
2952
+
2953
+ /**
2954
+ The MIT License (MIT)
2955
+
2956
+ Copyright (c) 2016-present react-component
2957
+
2958
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2959
+ of this software and associated documentation files (the "Software"), to deal
2960
+ in the Software without restriction, including without limitation the rights
2961
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2962
+ copies of the Software, and to permit persons to whom the Software is
2963
+ furnished to do so, subject to the following conditions:
2964
+
2965
+ The above copyright notice and this permission notice shall be included in
2966
+ all copies or substantial portions of the Software.
2967
+
2968
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
2969
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2970
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
2971
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2972
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2973
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2974
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2975
+
2976
+ Repository: https://github.com/react-component/upload
2977
+ */
2978
+ function getError(option, xhr) {
2979
+ const msg = `Cannot ${option.method} ${option.action} ${xhr.status}'`;
2980
+ const err = new Error(msg);
2981
+ err.status = xhr.status;
2982
+ err.method = option.method;
2983
+ err.url = option.action;
2984
+ return err;
2985
+ }
2986
+ function getBody(xhr) {
2987
+ const text = xhr.responseText || xhr.response;
2988
+ if (!text) {
2989
+ return text;
2990
+ }
2991
+ try {
2992
+ return JSON.parse(text);
2993
+ }
2994
+ catch (e) {
2995
+ return text;
2996
+ }
2997
+ }
2998
+ function request(option) {
2999
+ const xhr = new XMLHttpRequest();
3000
+ if (option.onProgress && xhr.upload) {
3001
+ xhr.upload.onprogress = (e) => {
3002
+ if (e.total > 0) {
3003
+ e.percent = (e.loaded / e.total) * 100;
3004
+ }
3005
+ if (option.onProgress) {
3006
+ option.onProgress(e);
3007
+ }
3008
+ };
3009
+ }
3010
+ const formData = new FormData();
3011
+ const data = option.data || {};
3012
+ Object.keys(data).forEach(key => {
3013
+ const value = data[key];
3014
+ // support key-value array data
3015
+ if (Array.isArray(value)) {
3016
+ value.forEach(item => {
3017
+ // { list: [ 11, 22 ] }
3018
+ // formData.append('list[]', 11);
3019
+ formData.append(`${key}[]`, item);
3020
+ });
3021
+ return;
3022
+ }
3023
+ formData.append(key, value);
3024
+ });
3025
+ const filename = option.filename || 'file';
3026
+ if (option.file instanceof Blob) {
3027
+ formData.append(filename, option.file, option.file.name);
3028
+ }
3029
+ else {
3030
+ formData.append(filename, option.file);
3031
+ }
3032
+ xhr.onerror = (e) => {
3033
+ if (option.onError) {
3034
+ option.onError(e);
3035
+ }
3036
+ };
3037
+ xhr.onload = () => {
3038
+ // allow success when 2xx status
3039
+ // see https://github.com/react-component/upload/issues/34
3040
+ if (xhr.status < 200 || xhr.status >= 300) {
3041
+ if (!option.onError) {
3042
+ return;
3043
+ }
3044
+ return option.onError(getError(option, xhr), getBody(xhr));
3045
+ }
3046
+ if (!option.onSuccess) {
3047
+ return;
3048
+ }
3049
+ return option.onSuccess(getBody(xhr), xhr);
3050
+ };
3051
+ xhr.open(option.method, option.action, true);
3052
+ // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
3053
+ if (option.withCredentials && 'withCredentials' in xhr) {
3054
+ xhr.withCredentials = true;
3055
+ }
3056
+ const headers = option.headers || {};
3057
+ // when set headers['X-Requested-With'] = null , can close default XHR header
3058
+ // see https://github.com/react-component/upload/issues/33
3059
+ if (headers['X-Requested-With'] !== null) {
3060
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
3061
+ }
3062
+ Object.keys(headers).forEach(h => {
3063
+ if (headers[h] !== null) {
3064
+ xhr.setRequestHeader(h, headers[h]);
3065
+ }
3066
+ });
3067
+ xhr.send(formData);
3068
+ return xhr;
3069
+ }
3070
+
3071
+ // String
3072
+
3073
+ var index = /*#__PURE__*/Object.freeze({
3074
+ __proto__: null,
3075
+ appendDeepest: appendDeepest,
3076
+ camelCase: camelCase,
3077
+ changeTagName: changeTagName,
3078
+ debug: debug,
3079
+ denormalizeValue: denormalizeValue,
3080
+ encode: encode,
3081
+ fileSize: fileSize,
3082
+ fixNumberedList: fixNumberedList,
3083
+ getBox: getBox,
3084
+ getCSS: getCSS,
3085
+ getDeepest: getDeepest,
3086
+ inString: inString,
3087
+ mergeNodes: mergeNodes,
3088
+ modifierText: modifierText,
3089
+ morph: morph,
3090
+ nodeAndView: nodeAndView,
3091
+ normalizeValue: normalizeValue,
3092
+ parseStyle: parseStyle,
3093
+ query: query,
3094
+ removeBr: removeBr,
3095
+ removeZWS: removeZWS,
3096
+ request: request,
3097
+ safeTemplate: safeTemplate,
3098
+ setBlockIndent: setBlockIndent,
3099
+ splitNodes: splitNodes,
3100
+ template: template,
3101
+ toHex: toHex,
3102
+ toNodeList: toNodeList,
3103
+ wrapNodeList: wrapNodeList
3104
+ });
3105
+
3106
+ class Fragment {
3107
+ constructor(fragment) {
3108
+ this.fragment = fragment !== null && fragment !== void 0 ? fragment : document.createDocumentFragment();
3109
+ }
3110
+ // Returns the descendants of the fragment which are selected by the specified CSS selector.
3111
+ find(selector) {
3112
+ const nodeList = [];
3113
+ let child = new Nodes(this.fragment.firstChild);
3114
+ while (child.length > 0) {
3115
+ if (child.matches(selector)) {
3116
+ nodeList.push(child.get(0));
3117
+ }
3118
+ else if (child.isElement) {
3119
+ child.find(selector).each(node => {
3120
+ nodeList.push(node);
3121
+ });
3122
+ }
3123
+ child = child.next();
3124
+ }
3125
+ return new Nodes(nodeList);
3126
+ }
3127
+ // Inserts the specified node as the last child.
3128
+ append(node) {
3129
+ node.each(nativeNode => {
3130
+ this.fragment.appendChild(nativeNode);
3131
+ });
3132
+ }
3133
+ }
3134
+
2977
3135
  const blockAttributeRules = {
2978
3136
  id: /^[\w-]+$/,
2979
3137
  class: /^[\w-]+$/,
@@ -3380,7 +3538,7 @@ function toBookmark(range, bookmark) {
3380
3538
  }
3381
3539
  if (focus.length > 0 && anchor.length === 0) {
3382
3540
  if (focus.isBox) {
3383
- const box = new Box(focus);
3541
+ const box = getBox(focus);
3384
3542
  if (box.getContainer().length === 0) {
3385
3543
  box.render();
3386
3544
  }
@@ -3801,7 +3959,7 @@ function addMark(range, value) {
3801
3959
  if (range.isCollapsed) {
3802
3960
  if (range.isBox) {
3803
3961
  const boxNode = range.startNode.closest('lake-box');
3804
- const box = new Box(boxNode);
3962
+ const box = getBox(boxNode);
3805
3963
  if (box.type === 'block') {
3806
3964
  const newBlock = query('<p><br /></p>');
3807
3965
  if (range.isBoxStart) {
@@ -4100,6 +4258,7 @@ var enUS = {
4100
4258
  fontColor: 'Font color',
4101
4259
  highlight: 'Highlight',
4102
4260
  image: 'Image',
4261
+ file: 'File',
4103
4262
  removeColor: 'Remove color',
4104
4263
  },
4105
4264
  link: {
@@ -4121,6 +4280,10 @@ var enUS = {
4121
4280
  zoomOut: 'Zoom out',
4122
4281
  zoomIn: 'Zoom in',
4123
4282
  },
4283
+ file: {
4284
+ download: 'Download',
4285
+ remove: 'Delete',
4286
+ },
4124
4287
  codeBlock: {
4125
4288
  langType: 'Select language',
4126
4289
  },
@@ -4170,6 +4333,7 @@ var zhCN = {
4170
4333
  fontColor: '文字颜色',
4171
4334
  highlight: '文字背景',
4172
4335
  image: '图片',
4336
+ file: '文件',
4173
4337
  removeColor: '默认',
4174
4338
  },
4175
4339
  link: {
@@ -4191,6 +4355,10 @@ var zhCN = {
4191
4355
  zoomOut: '缩小',
4192
4356
  zoomIn: '放大',
4193
4357
  },
4358
+ file: {
4359
+ download: '下载',
4360
+ remove: '删除',
4361
+ },
4194
4362
  codeBlock: {
4195
4363
  langType: '选择代码语言',
4196
4364
  },
@@ -4240,6 +4408,7 @@ var ja = {
4240
4408
  fontColor: '文字色',
4241
4409
  highlight: '文字の背景',
4242
4410
  image: '画像',
4411
+ file: 'ファイル',
4243
4412
  removeColor: 'デフォルト',
4244
4413
  },
4245
4414
  link: {
@@ -4261,6 +4430,10 @@ var ja = {
4261
4430
  zoomOut: '縮小',
4262
4431
  zoomIn: '拡大',
4263
4432
  },
4433
+ file: {
4434
+ download: 'ダウンロード',
4435
+ remove: '削除',
4436
+ },
4264
4437
  codeBlock: {
4265
4438
  langType: 'コード言語を選択',
4266
4439
  },
@@ -4310,6 +4483,7 @@ var ko = {
4310
4483
  fontColor: '글자 색상',
4311
4484
  highlight: '글자 배경',
4312
4485
  image: '이미지',
4486
+ file: '파일',
4313
4487
  removeColor: '기본색',
4314
4488
  },
4315
4489
  link: {
@@ -4331,6 +4505,10 @@ var ko = {
4331
4505
  zoomOut: '축소',
4332
4506
  zoomIn: '확대',
4333
4507
  },
4508
+ file: {
4509
+ download: '다운로드',
4510
+ remove: '삭제',
4511
+ },
4334
4512
  codeBlock: {
4335
4513
  langType: '코드언어 선택',
4336
4514
  },
@@ -4613,14 +4791,14 @@ class Dropdown {
4613
4791
  }
4614
4792
  }
4615
4793
 
4616
- var version = "0.1.7";
4794
+ var version = "0.1.9";
4617
4795
 
4618
4796
  // Inserts a box into the specified range.
4619
4797
  function insertBox(range, boxName, boxValue) {
4620
4798
  if (range.commonAncestor.isOutside) {
4621
4799
  return null;
4622
4800
  }
4623
- const box = new Box(boxName);
4801
+ const box = getBox(boxName);
4624
4802
  if (boxValue) {
4625
4803
  box.value = boxValue;
4626
4804
  }
@@ -4659,7 +4837,7 @@ function removeBox(range) {
4659
4837
  if (boxNode.length === 0) {
4660
4838
  return null;
4661
4839
  }
4662
- const box = new Box(boxNode);
4840
+ const box = getBox(boxNode);
4663
4841
  if (box.type === 'block') {
4664
4842
  const paragraph = query('<p><br /></p>');
4665
4843
  boxNode.before(paragraph);
@@ -4763,20 +4941,20 @@ class Selection {
4763
4941
  return new Range();
4764
4942
  }
4765
4943
  // Adds the saved range to the native selection.
4766
- addRangeToNativeSelection() {
4944
+ sync() {
4767
4945
  this.selection.removeAllRanges();
4768
4946
  this.selection.addRange(this.range.get());
4769
4947
  }
4770
- // Synchronizes the saved range with the range of the native selection.
4771
- syncByRange() {
4948
+ // Updates the saved range with the range of the native selection.
4949
+ updateByRange() {
4772
4950
  const newRange = this.getRangeFromNativeSelection();
4773
4951
  if (this.range.get() === newRange.get()) {
4774
4952
  return;
4775
4953
  }
4776
4954
  this.range = newRange;
4777
4955
  }
4778
- // Synchronizes the saved range with the range represented by the bookmark.
4779
- synByBookmark() {
4956
+ // Updates the saved range with the range represented by the bookmark.
4957
+ updateByBookmark() {
4780
4958
  const range = this.range;
4781
4959
  const container = this.container;
4782
4960
  const boxFocus = container.find('lake-box[focus]');
@@ -4785,7 +4963,7 @@ class Selection {
4785
4963
  anchor: new Nodes(),
4786
4964
  focus: boxFocus,
4787
4965
  });
4788
- this.addRangeToNativeSelection();
4966
+ this.sync();
4789
4967
  return;
4790
4968
  }
4791
4969
  const anchor = container.find('lake-bookmark[type="anchor"]');
@@ -4794,7 +4972,7 @@ class Selection {
4794
4972
  anchor,
4795
4973
  focus,
4796
4974
  });
4797
- this.addRangeToNativeSelection();
4975
+ this.sync();
4798
4976
  }
4799
4977
  getAppliedItems() {
4800
4978
  const appliedItems = [];
@@ -4901,8 +5079,6 @@ class Command {
4901
5079
  }
4902
5080
  }
4903
5081
 
4904
- const boxInstances = new Map();
4905
-
4906
5082
  // Saves and controls the history of the value of the editor.
4907
5083
  // Example:
4908
5084
  // before initialization: value: 'a', list: [], index: 0, canUndo: false
@@ -4980,7 +5156,7 @@ class History {
4980
5156
  const range = this.selection.range;
4981
5157
  const newContainer = this.container.clone(true);
4982
5158
  newContainer.find('lake-box').each(nativeNode => {
4983
- const box = new Box(nativeNode);
5159
+ const box = getBox(nativeNode);
4984
5160
  box.getContainer().empty();
4985
5161
  });
4986
5162
  if (range.commonAncestor.isOutside) {
@@ -5030,7 +5206,7 @@ class History {
5030
5206
  }
5031
5207
  this.index--;
5032
5208
  }
5033
- this.selection.synByBookmark();
5209
+ this.selection.updateByBookmark();
5034
5210
  debug(`History undone, the last index is ${this.index}`);
5035
5211
  }
5036
5212
  redo() {
@@ -5052,7 +5228,7 @@ class History {
5052
5228
  break;
5053
5229
  }
5054
5230
  }
5055
- this.selection.synByBookmark();
5231
+ this.selection.updateByBookmark();
5056
5232
  debug(`History redone, the last index is ${this.index}`);
5057
5233
  }
5058
5234
  continue() {
@@ -5159,17 +5335,17 @@ class BoxManager {
5159
5335
  getNames() {
5160
5336
  return Array.from(boxes.keys());
5161
5337
  }
5162
- getInstances(editor) {
5163
- let instanceMap = boxInstances.get(editor.container.id);
5338
+ getInstances(container) {
5339
+ let instanceMap = boxInstances.get(container.id);
5164
5340
  if (!instanceMap) {
5165
5341
  instanceMap = new Map();
5166
- boxInstances.set(editor.container.id, instanceMap);
5342
+ boxInstances.set(container.id, instanceMap);
5167
5343
  return instanceMap;
5168
5344
  }
5169
5345
  return instanceMap;
5170
5346
  }
5171
- rectifyInstances(editor) {
5172
- const instanceMap = this.getInstances(editor);
5347
+ rectifyInstances(container) {
5348
+ const instanceMap = this.getInstances(container);
5173
5349
  for (const box of instanceMap.values()) {
5174
5350
  if (!box.node.get(0).isConnected) {
5175
5351
  box.unmount();
@@ -5177,20 +5353,16 @@ class BoxManager {
5177
5353
  }
5178
5354
  }
5179
5355
  }
5180
- findAll(editor) {
5181
- return editor.container.find('lake-box');
5182
- }
5183
- renderAll(editor) {
5184
- this.rectifyInstances(editor);
5185
- const instanceMap = this.getInstances(editor);
5186
- this.findAll(editor).each(boxNativeNode => {
5187
- const boxNode = new Nodes(boxNativeNode);
5356
+ renderAll(container) {
5357
+ this.rectifyInstances(container);
5358
+ const instanceMap = this.getInstances(container);
5359
+ container.find('lake-box').each(boxNativeNode => {
5360
+ const boxNode = query(boxNativeNode);
5188
5361
  if (instanceMap.get(boxNode.id)) {
5189
5362
  return;
5190
5363
  }
5191
- const box = new Box(boxNode);
5364
+ const box = getBox(boxNode);
5192
5365
  box.render();
5193
- instanceMap.set(box.node.id, box);
5194
5366
  });
5195
5367
  }
5196
5368
  }
@@ -5218,6 +5390,22 @@ const defaultConfig = {
5218
5390
  indentWithTab: true,
5219
5391
  lang: 'en-US',
5220
5392
  minChangeSize: 5,
5393
+ onMessage: (type, message) => {
5394
+ if (type === 'success') {
5395
+ // eslint-disable-next-line no-console
5396
+ console.log(message);
5397
+ return;
5398
+ }
5399
+ if (type === 'warning') {
5400
+ // eslint-disable-next-line no-console
5401
+ console.warn(message);
5402
+ return;
5403
+ }
5404
+ if (type === 'error') {
5405
+ // eslint-disable-next-line no-console
5406
+ console.error(message);
5407
+ }
5408
+ },
5221
5409
  };
5222
5410
  class Editor {
5223
5411
  constructor(config) {
@@ -5228,20 +5416,41 @@ class Editor {
5228
5416
  selectedNameMap: new Map(),
5229
5417
  selectedValuesMap: new Map(),
5230
5418
  };
5231
- this.isComposing = false;
5232
- this.event = new EventEmitter();
5233
- this.box = Editor.box;
5419
+ this.isComposing = false;
5420
+ this.event = new EventEmitter();
5421
+ this.box = Editor.box;
5422
+ this.copyListener = event => {
5423
+ const range = this.selection.range;
5424
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5425
+ return;
5426
+ }
5427
+ this.event.emit('copy', event);
5428
+ };
5429
+ this.cutListener = event => {
5430
+ const range = this.selection.range;
5431
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5432
+ return;
5433
+ }
5434
+ this.event.emit('cut', event);
5435
+ };
5436
+ this.pasteListener = event => {
5437
+ const range = this.selection.range;
5438
+ if (range.commonAncestor.closestContainer().get(0) !== this.container.get(0)) {
5439
+ return;
5440
+ }
5441
+ this.event.emit('paste', event);
5442
+ };
5234
5443
  this.beforeunloadListener = () => {
5235
5444
  this.history.save();
5236
5445
  };
5237
5446
  this.selectionchangeListener = () => {
5238
- this.selection.syncByRange();
5447
+ this.selection.updateByRange();
5239
5448
  this.updateBoxSelectionStyle();
5240
5449
  this.emitStateChangeEvent();
5241
5450
  };
5242
5451
  this.clickListener = event => {
5243
5452
  const targetNode = new Nodes(event.target);
5244
- if (targetNode.closest('.lake-popup').length > 0) {
5453
+ if (!targetNode.get(0).isConnected || targetNode.closest('.lake-popup').length > 0) {
5245
5454
  return;
5246
5455
  }
5247
5456
  this.event.emit('click', targetNode);
@@ -5257,8 +5466,8 @@ class Editor {
5257
5466
  const range = this.selection.range;
5258
5467
  const clonedRange = range.clone();
5259
5468
  clonedRange.adaptBox();
5260
- this.box.findAll(this).each(boxNode => {
5261
- const box = new Box(boxNode);
5469
+ this.container.find('lake-box').each(boxNativeNode => {
5470
+ const box = getBox(boxNativeNode);
5262
5471
  const boxContainer = box.getContainer();
5263
5472
  if (boxContainer.length === 0) {
5264
5473
  return;
@@ -5269,6 +5478,7 @@ class Editor {
5269
5478
  boxContainer.removeClass('lake-box-selected');
5270
5479
  boxContainer.removeClass('lake-box-focused');
5271
5480
  boxContainer.addClass('lake-box-activated');
5481
+ box.event.emit('focus');
5272
5482
  return;
5273
5483
  }
5274
5484
  }
@@ -5278,16 +5488,19 @@ class Editor {
5278
5488
  boxContainer.removeClass('lake-box-hovered');
5279
5489
  boxContainer.removeClass('lake-box-selected');
5280
5490
  boxContainer.addClass('lake-box-focused');
5491
+ box.event.emit('focus');
5281
5492
  }
5282
5493
  else {
5283
5494
  boxContainer.removeClass('lake-box-focused');
5284
5495
  boxContainer.addClass('lake-box-selected');
5496
+ box.event.emit('blur');
5285
5497
  }
5286
5498
  return;
5287
5499
  }
5288
5500
  boxContainer.removeClass('lake-box-activated');
5289
5501
  boxContainer.removeClass('lake-box-focused');
5290
5502
  boxContainer.removeClass('lake-box-selected');
5503
+ box.event.emit('blur');
5291
5504
  });
5292
5505
  this.event.emit('boxselectionstylechange');
5293
5506
  }, 50, {
@@ -5345,6 +5558,7 @@ class Editor {
5345
5558
  this.rectifyContent();
5346
5559
  this.emitStateChangeEvent();
5347
5560
  this.togglePlaceholderClass(value);
5561
+ this.scrollToCaret();
5348
5562
  this.event.emit('change', value);
5349
5563
  };
5350
5564
  if (!config.root) {
@@ -5391,7 +5605,7 @@ class Editor {
5391
5605
  const range = selection.range;
5392
5606
  const stripNode = range.startNode.closest('.lake-box-strip');
5393
5607
  const boxNode = stripNode.closest('lake-box');
5394
- const box = new Box(boxNode);
5608
+ const box = getBox(boxNode);
5395
5609
  if (box.type === 'inline') {
5396
5610
  if (range.isBoxStart) {
5397
5611
  range.setStartBefore(boxNode);
@@ -5473,15 +5687,15 @@ class Editor {
5473
5687
  }
5474
5688
  bindHistoryEvents() {
5475
5689
  this.history.event.on('undo', value => {
5476
- this.box.renderAll(this);
5690
+ this.box.renderAll(this.container);
5477
5691
  this.emitChangeEvent(value);
5478
5692
  });
5479
5693
  this.history.event.on('redo', value => {
5480
- this.box.renderAll(this);
5694
+ this.box.renderAll(this.container);
5481
5695
  this.emitChangeEvent(value);
5482
5696
  });
5483
5697
  this.history.event.on('save', value => {
5484
- this.box.rectifyInstances(this);
5698
+ this.box.rectifyInstances(this.container);
5485
5699
  this.emitChangeEvent(value);
5486
5700
  });
5487
5701
  }
@@ -5560,6 +5774,66 @@ class Editor {
5560
5774
  blur() {
5561
5775
  this.container.blur();
5562
5776
  }
5777
+ // Scrolls to the caret or the range of the selection.
5778
+ scrollToCaret() {
5779
+ // Creates an artificial caret that is the same size as the caret at the current caret position.
5780
+ const rangeRect = this.selection.range.getRect();
5781
+ const containerRect = this.container.get(0).getBoundingClientRect();
5782
+ const artificialCaret = query('<div class="lake-artificial-caret" />');
5783
+ const left = rangeRect.x - containerRect.x;
5784
+ const top = rangeRect.y - containerRect.y;
5785
+ artificialCaret.css({
5786
+ position: 'absolute',
5787
+ top: `${top}px`,
5788
+ left: `${left}px`,
5789
+ width: `${rangeRect.width}px`,
5790
+ height: `${rangeRect.height}px`,
5791
+ // background: 'red',
5792
+ 'z-index': '-1',
5793
+ });
5794
+ this.overlayContainer.find('.lake-artificial-caret').remove();
5795
+ this.overlayContainer.append(artificialCaret);
5796
+ // Scrolls the artificial caret element into the visible area of the browser window
5797
+ // if it's not already within the visible area of the browser window.
5798
+ // If the element is already within the visible area of the browser window, then no scrolling takes place.
5799
+ let scrollX;
5800
+ let scrollY;
5801
+ let viewportWidth;
5802
+ let viewportHeight;
5803
+ const viewport = this.container.closestScroller();
5804
+ if (viewport.length > 0) {
5805
+ const nativeViewport = viewport.get(0);
5806
+ const viewportRect = nativeViewport.getBoundingClientRect();
5807
+ scrollX = nativeViewport.scrollLeft;
5808
+ scrollY = nativeViewport.scrollTop;
5809
+ viewportWidth = viewportRect.width;
5810
+ viewportHeight = viewportRect.height;
5811
+ }
5812
+ else {
5813
+ const nativeContainerWrapper = this.containerWrapper.get(0);
5814
+ scrollX = window.scrollX;
5815
+ scrollY = window.scrollY;
5816
+ viewportWidth = window.innerWidth - nativeContainerWrapper.offsetLeft;
5817
+ viewportHeight = window.innerHeight - nativeContainerWrapper.offsetTop;
5818
+ }
5819
+ let needScroll = false;
5820
+ let alignToTop = true;
5821
+ if (left < scrollX || left > scrollX + viewportWidth) {
5822
+ needScroll = true;
5823
+ }
5824
+ if (top < scrollY) {
5825
+ needScroll = true;
5826
+ alignToTop = true;
5827
+ }
5828
+ else if (top > scrollY + viewportHeight) {
5829
+ needScroll = true;
5830
+ alignToTop = false;
5831
+ }
5832
+ if (needScroll) {
5833
+ artificialCaret.get(0).scrollIntoView(alignToTop);
5834
+ }
5835
+ artificialCaret.remove();
5836
+ }
5563
5837
  // Sets the specified HTML string to the editor area.
5564
5838
  setValue(value) {
5565
5839
  value = normalizeValue(value);
@@ -5568,8 +5842,8 @@ class Editor {
5568
5842
  this.container.empty();
5569
5843
  this.togglePlaceholderClass(htmlParser.getHTML());
5570
5844
  this.container.append(fragment);
5571
- Editor.box.renderAll(this);
5572
- this.selection.synByBookmark();
5845
+ Editor.box.renderAll(this.container);
5846
+ this.selection.updateByBookmark();
5573
5847
  }
5574
5848
  // Returns the contents from the editor.
5575
5849
  getValue() {
@@ -5579,21 +5853,35 @@ class Editor {
5579
5853
  this.selection.toBookmark(bookmark);
5580
5854
  return value;
5581
5855
  }
5856
+ // Sets the current range to the center position of the box.
5857
+ selectBox(box) {
5858
+ let boxNode = box;
5859
+ if (box instanceof Box) {
5860
+ boxNode = box.node;
5861
+ }
5862
+ else {
5863
+ boxNode = box;
5864
+ }
5865
+ this.selection.range.selectBox(boxNode);
5866
+ }
5582
5867
  // Inserts a box into the position of the selection.
5583
5868
  insertBox(boxName, boxValue) {
5584
5869
  const box = insertBox(this.selection.range, boxName, boxValue);
5585
5870
  if (!box) {
5586
5871
  throw new Error(`Box '${boxName}' cannot be inserted outside the editor.`);
5587
5872
  }
5588
- const instanceMap = this.box.getInstances(this);
5873
+ const instanceMap = this.box.getInstances(this.container);
5589
5874
  instanceMap.set(box.node.id, box);
5590
5875
  return box;
5591
5876
  }
5592
5877
  // Removes the selected box.
5593
- removeBox() {
5594
- const box = removeBox(this.selection.range);
5878
+ removeBox(box = null) {
5879
+ if (box) {
5880
+ this.selectBox(box);
5881
+ }
5882
+ box = removeBox(this.selection.range);
5595
5883
  if (box) {
5596
- const instanceMap = this.box.getInstances(this);
5884
+ const instanceMap = this.box.getInstances(this.container);
5597
5885
  instanceMap.delete(box.node.id);
5598
5886
  }
5599
5887
  return box;
@@ -5612,14 +5900,17 @@ class Editor {
5612
5900
  this.container.append(fragment);
5613
5901
  Editor.plugin.loadAll(this);
5614
5902
  if (!this.readonly) {
5615
- this.selection.synByBookmark();
5903
+ this.selection.updateByBookmark();
5616
5904
  this.history.save();
5617
5905
  }
5618
- Editor.box.renderAll(this);
5906
+ Editor.box.renderAll(this.container);
5619
5907
  if (this.toolbar) {
5620
5908
  this.toolbar.render(this);
5621
5909
  }
5910
+ document.addEventListener('copy', this.copyListener);
5622
5911
  if (!this.readonly) {
5912
+ document.addEventListener('cut', this.cutListener);
5913
+ document.addEventListener('paste', this.pasteListener);
5623
5914
  window.addEventListener('beforeunload', this.beforeunloadListener);
5624
5915
  document.addEventListener('selectionchange', this.selectionchangeListener);
5625
5916
  document.addEventListener('click', this.clickListener);
@@ -5635,7 +5926,10 @@ class Editor {
5635
5926
  this.history.event.removeAllListeners();
5636
5927
  this.root.empty();
5637
5928
  this.popupContainer.remove();
5929
+ document.removeEventListener('copy', this.copyListener);
5638
5930
  if (!this.readonly) {
5931
+ document.removeEventListener('cut', this.cutListener);
5932
+ document.removeEventListener('paste', this.pasteListener);
5639
5933
  window.removeEventListener('beforeunload', this.beforeunloadListener);
5640
5934
  document.removeEventListener('selectionchange', this.selectionchangeListener);
5641
5935
  document.removeEventListener('click', this.clickListener);
@@ -6266,15 +6560,26 @@ const toolbarItems = [
6266
6560
  accept: 'image/*',
6267
6561
  multiple: true,
6268
6562
  },
6563
+ {
6564
+ name: 'file',
6565
+ type: 'upload',
6566
+ icon: icons.get('attachment'),
6567
+ tooltip: locale => locale.toolbar.file(),
6568
+ accept: '*',
6569
+ multiple: true,
6570
+ },
6269
6571
  ];
6270
6572
 
6271
- function uploadImage(config) {
6272
- const { editor, file, onError, onSuccess } = config;
6273
- const { requestMethod, requestAction, requestTypes } = editor.config.image;
6573
+ function uploadFile(config) {
6574
+ const { editor, name, file, onError, onSuccess } = config;
6575
+ const { requestMethod, requestAction, requestTypes } = editor.config[name];
6274
6576
  if (requestTypes.indexOf(file.type) < 0) {
6275
- throw new Error(`Cannot upload file because its type '${file.type}' is not found in ['${requestTypes.join('\', \'')}'].`);
6577
+ if (onError) {
6578
+ onError(`File '${file.name}' is not allowed for uploading.`);
6579
+ }
6580
+ throw new Error(`Cannot upload file '${file.name}' because its type '${file.type}' is not found in ['${requestTypes.join('\', \'')}'].`);
6276
6581
  }
6277
- const box = editor.insertBox('image', {
6582
+ const box = editor.insertBox(name, {
6278
6583
  url: URL.createObjectURL(file),
6279
6584
  status: 'uploading',
6280
6585
  name: file.name,
@@ -6293,7 +6598,7 @@ function uploadImage(config) {
6293
6598
  box.updateValue('status', 'error');
6294
6599
  box.render();
6295
6600
  if (onError) {
6296
- onError();
6601
+ onError(error.toString());
6297
6602
  }
6298
6603
  },
6299
6604
  onSuccess: body => {
@@ -6301,7 +6606,7 @@ function uploadImage(config) {
6301
6606
  box.updateValue('status', 'error');
6302
6607
  box.render();
6303
6608
  if (onError) {
6304
- onError();
6609
+ onError('Cannot find the url field.');
6305
6610
  }
6306
6611
  return;
6307
6612
  }
@@ -6344,9 +6649,9 @@ const defaultItems = [
6344
6649
  'blockQuote',
6345
6650
  'hr',
6346
6651
  ];
6347
- const toolbarItemMap = new Map();
6652
+ const toolbarItemMap$1 = new Map();
6348
6653
  toolbarItems.forEach(item => {
6349
- toolbarItemMap.set(item.name, item);
6654
+ toolbarItemMap$1.set(item.name, item);
6350
6655
  });
6351
6656
  class Toolbar {
6352
6657
  constructor(config) {
@@ -6433,9 +6738,11 @@ class Toolbar {
6433
6738
  const target = event.target;
6434
6739
  const files = target.files || [];
6435
6740
  for (const file of files) {
6436
- uploadImage({
6741
+ uploadFile({
6437
6742
  editor,
6743
+ name: item.name,
6438
6744
  file,
6745
+ onError: error => editor.config.onMessage('error', error),
6439
6746
  });
6440
6747
  }
6441
6748
  });
@@ -6510,7 +6817,7 @@ class Toolbar {
6510
6817
  }
6511
6818
  let item;
6512
6819
  if (typeof name === 'string') {
6513
- item = toolbarItemMap.get(name);
6820
+ item = toolbarItemMap$1.get(name);
6514
6821
  if (!item) {
6515
6822
  return;
6516
6823
  }
@@ -6547,7 +6854,7 @@ const hrBox = {
6547
6854
  box.useEffect(() => {
6548
6855
  const hrNode = box.getContainer().find('.lake-hr');
6549
6856
  hrNode.on('click', () => {
6550
- editor.selection.range.selectBox(box.node);
6857
+ editor.selectBox(box);
6551
6858
  });
6552
6859
  });
6553
6860
  return '<div class="lake-hr"><hr /></div>';
@@ -6691,7 +6998,7 @@ function openFullScreen(box) {
6691
6998
  let currentIndex = 0;
6692
6999
  const allImageBox = editor.container.find('lake-box[name="image"]');
6693
7000
  allImageBox.each((node, index) => {
6694
- const imageBox = new Box(node);
7001
+ const imageBox = getBox(node);
6695
7002
  const imageValue = imageBox.value;
6696
7003
  if (imageValue.status !== 'done') {
6697
7004
  return;
@@ -6764,31 +7071,24 @@ function openFullScreen(box) {
6764
7071
  }
6765
7072
  return placeholderSrc;
6766
7073
  });
7074
+ let savedRange;
6767
7075
  lightbox.on('openingAnimationEnd', () => {
7076
+ savedRange = editor.selection.range;
6768
7077
  box.event.emit('openfullscreen');
6769
7078
  });
6770
7079
  lightbox.on('destroy', () => {
6771
7080
  window.setTimeout(() => {
7081
+ if (savedRange) {
7082
+ // fix(image): lose focus when zooming in the iOS
7083
+ editor.selection.range = savedRange;
7084
+ editor.selection.sync();
7085
+ }
6772
7086
  box.event.emit('closefullscreen');
6773
7087
  }, 0);
6774
7088
  });
6775
7089
  lightbox.init();
6776
7090
  lightbox.loadAndOpen(currentIndex);
6777
7091
  }
6778
- // Removes current box.
6779
- function removeImageBox(box) {
6780
- const editor = box.getEditor();
6781
- if (!editor) {
6782
- return;
6783
- }
6784
- const xhr = box.getData('xhr');
6785
- if (xhr) {
6786
- xhr.abort();
6787
- }
6788
- editor.selection.range.selectBox(box.node);
6789
- editor.removeBox();
6790
- editor.history.save();
6791
- }
6792
7092
  // Displays error icon and filename.
6793
7093
  function renderError(imageNode, box) {
6794
7094
  return __awaiter(this, void 0, void 0, function* () {
@@ -6980,7 +7280,7 @@ const imageBox = {
6980
7280
  }
6981
7281
  }
6982
7282
  if (container.first().length === 0) {
6983
- // The code below is for unit testing because some test cases need to
7283
+ // The following code is for unit testing because some test cases need to
6984
7284
  // select the content of the box before it is completely loaded.
6985
7285
  // Example:
6986
7286
  // range.setStart(box.getContainer(), 1);
@@ -7012,13 +7312,19 @@ const imageBox = {
7012
7312
  else {
7013
7313
  imageNode.find('.lake-button-remove').on('click', event => {
7014
7314
  event.stopPropagation();
7015
- removeImageBox(box);
7315
+ const xhr = box.getData('xhr');
7316
+ if (xhr) {
7317
+ xhr.abort();
7318
+ }
7319
+ editor.removeBox(box);
7320
+ editor.history.save();
7321
+ editor.selection.sync();
7016
7322
  });
7017
7323
  }
7018
7324
  box.event.emit('render');
7019
7325
  });
7020
7326
  imageNode.on('click', () => {
7021
- editor.selection.range.selectBox(box.node);
7327
+ editor.selectBox(box);
7022
7328
  });
7023
7329
  },
7024
7330
  html: box => {
@@ -7027,6 +7333,232 @@ const imageBox = {
7027
7333
  },
7028
7334
  };
7029
7335
 
7336
+ const toolbarItemMap = new Map();
7337
+ class BoxToolbar {
7338
+ constructor(config) {
7339
+ this.placement = 'top';
7340
+ this.buttonItemList = [];
7341
+ this.dropdownItemList = [];
7342
+ this.root = query(config.root);
7343
+ this.editor = config.editor;
7344
+ this.box = config.box;
7345
+ this.items = config.items;
7346
+ if (config.placement) {
7347
+ this.placement = config.placement;
7348
+ }
7349
+ this.container = query('<div class="lake-box-toolbar" />');
7350
+ this.root.addClass('lake-custom-properties');
7351
+ }
7352
+ appendDivider() {
7353
+ this.container.append('<div class="lake-box-toolbar-divider" />');
7354
+ }
7355
+ appendButton(item) {
7356
+ const button = new Button({
7357
+ root: this.container,
7358
+ name: item.name,
7359
+ icon: item.icon,
7360
+ tooltip: typeof item.tooltip === 'string' ? item.tooltip : item.tooltip(this.editor.locale),
7361
+ tabIndex: -1,
7362
+ onClick: () => {
7363
+ item.onClick(this.box, item.name);
7364
+ },
7365
+ });
7366
+ button.render();
7367
+ }
7368
+ appendDropdown(item) {
7369
+ const dropdown = new Dropdown({
7370
+ root: this.container,
7371
+ locale: this.editor.locale,
7372
+ name: item.name,
7373
+ icon: item.icon,
7374
+ accentIcon: item.accentIcon,
7375
+ downIcon: item.downIcon,
7376
+ defaultValue: item.defaultValue,
7377
+ tooltip: item.tooltip,
7378
+ width: item.width,
7379
+ menuType: item.menuType,
7380
+ menuItems: item.menuItems,
7381
+ tabIndex: -1,
7382
+ placement: this.placement === 'top' ? 'bottom' : 'top',
7383
+ onSelect: value => {
7384
+ item.onSelect(this.box, value);
7385
+ },
7386
+ });
7387
+ dropdown.render();
7388
+ }
7389
+ updatePosition() {
7390
+ const boxNode = this.box.node;
7391
+ const position = nodeAndView(boxNode);
7392
+ if (position.left < 0 || position.right < 0 || position.top < 0 || position.bottom < 0) {
7393
+ this.container.hide();
7394
+ return;
7395
+ }
7396
+ this.container.show('flex');
7397
+ const boxNativeNode = this.box.node.get(0);
7398
+ const boxRect = boxNativeNode.getBoundingClientRect();
7399
+ const boxX = boxRect.x + window.scrollX;
7400
+ const boxY = boxRect.y + window.scrollY;
7401
+ const left = (boxX + boxRect.width / 2 - this.container.width() / 2).toFixed(1);
7402
+ const top = (boxY - this.container.height() - 6).toFixed(1);
7403
+ this.container.css({
7404
+ left: `${left}px`,
7405
+ top: `${top}px`,
7406
+ });
7407
+ }
7408
+ // Renders a toolbar for the specified box.
7409
+ render() {
7410
+ this.root.empty();
7411
+ this.root.append(this.container);
7412
+ this.items.forEach(name => {
7413
+ if (name === '|') {
7414
+ this.appendDivider();
7415
+ return;
7416
+ }
7417
+ let item;
7418
+ if (typeof name === 'string') {
7419
+ item = toolbarItemMap.get(name);
7420
+ if (!item) {
7421
+ return;
7422
+ }
7423
+ }
7424
+ else {
7425
+ item = name;
7426
+ }
7427
+ if (item.type === 'button') {
7428
+ this.buttonItemList.push(item);
7429
+ this.appendButton(item);
7430
+ return;
7431
+ }
7432
+ if (item.type === 'dropdown') {
7433
+ this.dropdownItemList.push(item);
7434
+ this.appendDropdown(item);
7435
+ }
7436
+ });
7437
+ this.updatePosition();
7438
+ }
7439
+ unmount() {
7440
+ this.container.remove();
7441
+ }
7442
+ }
7443
+
7444
+ const boxToolbarItems = [
7445
+ {
7446
+ name: 'download',
7447
+ type: 'button',
7448
+ icon: icons.get('download'),
7449
+ tooltip: locale => locale.file.download(),
7450
+ onClick: box => {
7451
+ window.open(box.value.url);
7452
+ },
7453
+ },
7454
+ {
7455
+ name: 'remove',
7456
+ type: 'button',
7457
+ icon: icons.get('remove'),
7458
+ tooltip: locale => locale.file.remove(),
7459
+ onClick: box => {
7460
+ const editor = box.getEditor();
7461
+ if (!editor) {
7462
+ return;
7463
+ }
7464
+ editor.removeBox(box);
7465
+ editor.history.save();
7466
+ editor.selection.sync();
7467
+ },
7468
+ },
7469
+ ];
7470
+ function appendContent(fileNode, box) {
7471
+ return __awaiter(this, void 0, void 0, function* () {
7472
+ const editor = box.getEditor();
7473
+ if (!editor) {
7474
+ return;
7475
+ }
7476
+ const value = box.value;
7477
+ const infoNode = query(safeTemplate `
7478
+ <div class="lake-file-info">
7479
+ <div class="lake-file-type"></div>
7480
+ <div class="lake-file-name">${value.name} (${fileSize(value.size)})</div>
7481
+ </div>
7482
+ `);
7483
+ const typeNode = infoNode.find('.lake-file-type');
7484
+ if (value.status === 'uploading') {
7485
+ const percent = Math.round(value.percent || 0);
7486
+ const progressNode = query(safeTemplate `
7487
+ <div class="lake-progress">
7488
+ <div class="lake-percent">${percent} %</div>
7489
+ </div>
7490
+ `);
7491
+ const circleNotchIcon = icons.get('circleNotch');
7492
+ if (circleNotchIcon) {
7493
+ progressNode.prepend(circleNotchIcon);
7494
+ }
7495
+ typeNode.replaceWith(progressNode);
7496
+ }
7497
+ else {
7498
+ const fileIcon = value.status === 'error' ? icons.get('warningCircle') : icons.get('file');
7499
+ if (fileIcon) {
7500
+ typeNode.append(fileIcon);
7501
+ }
7502
+ }
7503
+ fileNode.append(infoNode);
7504
+ });
7505
+ }
7506
+ const fileBox = {
7507
+ type: 'inline',
7508
+ name: 'file',
7509
+ render: box => {
7510
+ const editor = box.getEditor();
7511
+ if (!editor) {
7512
+ return;
7513
+ }
7514
+ const value = box.value;
7515
+ const container = box.getContainer();
7516
+ const fileNode = query('<div class="lake-file" />');
7517
+ fileNode.addClass(`lake-file-${value.status}`);
7518
+ if (editor.readonly) {
7519
+ fileNode.addClass('lake-file-readonly');
7520
+ }
7521
+ appendContent(fileNode, box);
7522
+ container.empty();
7523
+ container.append(fileNode);
7524
+ if (!editor.readonly) {
7525
+ fileNode.on('click', () => {
7526
+ editor.selectBox(box);
7527
+ });
7528
+ let toolbar = null;
7529
+ const scrollListener = () => {
7530
+ if (toolbar) {
7531
+ toolbar.updatePosition();
7532
+ }
7533
+ };
7534
+ box.event.on('focus', () => {
7535
+ const items = value.status === 'done' ? boxToolbarItems : boxToolbarItems.filter(item => item.name === 'remove');
7536
+ toolbar = new BoxToolbar({
7537
+ root: editor.popupContainer,
7538
+ editor,
7539
+ box,
7540
+ items,
7541
+ });
7542
+ toolbar.render();
7543
+ editor.root.on('scroll', scrollListener);
7544
+ });
7545
+ box.event.on('blur', () => {
7546
+ if (toolbar) {
7547
+ toolbar.unmount();
7548
+ toolbar = null;
7549
+ }
7550
+ editor.root.off('scroll', scrollListener);
7551
+ });
7552
+ }
7553
+ else {
7554
+ fileNode.on('click', () => {
7555
+ window.open(value.url);
7556
+ });
7557
+ }
7558
+ box.event.emit('render');
7559
+ },
7560
+ };
7561
+
7030
7562
  const config = {
7031
7563
  comment: '#57606a',
7032
7564
  name: '#444d56',
@@ -7131,7 +7663,7 @@ const codeBlockBox = {
7131
7663
  Please check if the "lake-codemirror" library is added to this page.
7132
7664
  `.trim());
7133
7665
  codeBlockNode.on('click', () => {
7134
- editor.selection.range.selectBox(box.node);
7666
+ editor.selectBox(box);
7135
7667
  });
7136
7668
  return;
7137
7669
  }
@@ -7219,10 +7751,7 @@ const codeBlockBox = {
7219
7751
  };
7220
7752
 
7221
7753
  var copy = (editor) => {
7222
- if (editor.readonly) {
7223
- return;
7224
- }
7225
- editor.container.on('copy', event => {
7754
+ editor.event.on('copy', event => {
7226
7755
  const range = editor.selection.range;
7227
7756
  if (range.isInsideBox) {
7228
7757
  return;
@@ -7239,7 +7768,7 @@ var copy = (editor) => {
7239
7768
  if (!dataTransfer) {
7240
7769
  return;
7241
7770
  }
7242
- const box = new Box(boxNode);
7771
+ const box = getBox(boxNode);
7243
7772
  const content = box.getHTML();
7244
7773
  dataTransfer.setData('text/html', content);
7245
7774
  });
@@ -7249,7 +7778,7 @@ var cut = (editor) => {
7249
7778
  if (editor.readonly) {
7250
7779
  return;
7251
7780
  }
7252
- editor.container.on('cut', event => {
7781
+ editor.event.on('cut', event => {
7253
7782
  const range = editor.selection.range;
7254
7783
  if (range.isInsideBox) {
7255
7784
  return;
@@ -7266,7 +7795,7 @@ var cut = (editor) => {
7266
7795
  if (!dataTransfer) {
7267
7796
  return;
7268
7797
  }
7269
- const box = new Box(boxNode);
7798
+ const box = getBox(boxNode);
7270
7799
  const content = box.getHTML();
7271
7800
  dataTransfer.setData('text/html', content);
7272
7801
  editor.removeBox();
@@ -7332,7 +7861,7 @@ function insertFirstNode(editor, otherNode) {
7332
7861
  const range = editor.selection.range;
7333
7862
  const boxNode = range.startNode.closest('lake-box');
7334
7863
  if (boxNode.length > 0) {
7335
- const box = new Box(boxNode);
7864
+ const box = getBox(boxNode);
7336
7865
  if (box.type === 'inline') {
7337
7866
  if (range.isBoxStart) {
7338
7867
  range.setStartBefore(boxNode);
@@ -7363,7 +7892,7 @@ function insertFirstNode(editor, otherNode) {
7363
7892
  }
7364
7893
  const block = range.startNode.closestBlock();
7365
7894
  if (otherNode.isBlockBox) {
7366
- const box = new Box(otherNode);
7895
+ const box = getBox(otherNode);
7367
7896
  const value = otherNode.attr('value') !== '' ? box.value : undefined;
7368
7897
  editor.insertBox(box.name, value);
7369
7898
  otherNode.remove();
@@ -7375,7 +7904,7 @@ function insertFirstNode(editor, otherNode) {
7375
7904
  if (block.isEmpty && block.name === 'p') {
7376
7905
  block.replaceWith(otherNode);
7377
7906
  otherNode.find('lake-box').each(node => {
7378
- new Box(node).render();
7907
+ getBox(node).render();
7379
7908
  });
7380
7909
  range.shrinkAfter(otherNode);
7381
7910
  return;
@@ -7433,7 +7962,7 @@ var paste = (editor) => {
7433
7962
  if (editor.readonly) {
7434
7963
  return;
7435
7964
  }
7436
- editor.container.on('paste', event => {
7965
+ editor.event.on('paste', event => {
7437
7966
  const { requestTypes } = editor.config.image;
7438
7967
  const range = editor.selection.range;
7439
7968
  if (range.isInsideBox) {
@@ -7449,9 +7978,11 @@ var paste = (editor) => {
7449
7978
  if (dataTransfer.files.length > 0) {
7450
7979
  for (const file of dataTransfer.files) {
7451
7980
  if (requestTypes.indexOf(file.type) >= 0) {
7452
- uploadImage({
7981
+ uploadFile({
7453
7982
  editor,
7983
+ name: file.type.indexOf('image/') === 0 ? 'image' : 'file',
7454
7984
  file,
7985
+ onError: error => editor.config.onMessage('error', error),
7455
7986
  });
7456
7987
  }
7457
7988
  }
@@ -7474,7 +8005,155 @@ var paste = (editor) => {
7474
8005
  editor.event.emit('beforepaste', fragment);
7475
8006
  fixClipboardData(fragment);
7476
8007
  pasteFragment(editor, fragment);
7477
- editor.box.renderAll(editor);
8008
+ editor.box.renderAll(editor.container);
8009
+ });
8010
+ };
8011
+
8012
+ var drop = (editor) => {
8013
+ if (editor.readonly) {
8014
+ return;
8015
+ }
8016
+ let draggedNode = null;
8017
+ let dropIndication = null;
8018
+ let targetBlock = null;
8019
+ let dropPosition = 'bottom';
8020
+ // The dragstart event is fired when the user starts dragging an element or text selection.
8021
+ editor.container.on('dragstart', event => {
8022
+ draggedNode = null;
8023
+ const dragEvent = event;
8024
+ const dataTransfer = dragEvent.dataTransfer;
8025
+ if (!dataTransfer) {
8026
+ return;
8027
+ }
8028
+ dataTransfer.effectAllowed = 'move';
8029
+ // set the dragged node
8030
+ const targetNode = query(dragEvent.target);
8031
+ const boxNode = targetNode.closest('lake-box');
8032
+ if (boxNode.length === 0) {
8033
+ dragEvent.preventDefault();
8034
+ return;
8035
+ }
8036
+ const box = getBox(boxNode);
8037
+ if (box.type === 'inline') {
8038
+ dragEvent.preventDefault();
8039
+ return;
8040
+ }
8041
+ draggedNode = boxNode;
8042
+ // prepare an indication rod
8043
+ dropIndication = query(safeTemplate `
8044
+ <div class="lake-drop-indication">
8045
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
8046
+ <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>
8047
+ </svg>
8048
+ </div>
8049
+ `);
8050
+ editor.overlayContainer.append(dropIndication);
8051
+ });
8052
+ // The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).
8053
+ editor.container.on('dragover', event => {
8054
+ const dragEvent = event;
8055
+ dragEvent.preventDefault();
8056
+ const dataTransfer = dragEvent.dataTransfer;
8057
+ if (!dataTransfer) {
8058
+ return;
8059
+ }
8060
+ dataTransfer.dropEffect = 'move';
8061
+ if (!dropIndication) {
8062
+ return;
8063
+ }
8064
+ const targetNode = query(dragEvent.target);
8065
+ if (targetNode.isContainer) {
8066
+ return;
8067
+ }
8068
+ const targetBoxNode = targetNode.closest('lake-box');
8069
+ if (targetBoxNode.length > 0) {
8070
+ if (targetBoxNode.isBlockBox) {
8071
+ targetBlock = targetBoxNode;
8072
+ }
8073
+ else {
8074
+ targetBlock = targetBoxNode.closestBlock();
8075
+ }
8076
+ }
8077
+ else {
8078
+ targetBlock = targetNode.closestBlock();
8079
+ }
8080
+ const containerRect = editor.container.get(0).getBoundingClientRect();
8081
+ let targetBlcokRect = targetBlock.get(0).getBoundingClientRect();
8082
+ dropPosition = 'bottom';
8083
+ let left = targetBlcokRect.x - containerRect.x;
8084
+ let top = targetBlcokRect.y + targetBlcokRect.height - containerRect.y + (parseInt(targetBlock.computedCSS('margin-bottom'), 10) / 2);
8085
+ if (dragEvent.clientY < targetBlcokRect.y + (targetBlcokRect.height / 2)) {
8086
+ const prevBlock = targetBlock.prev();
8087
+ if (prevBlock.length > 0 && prevBlock.isBlock || prevBlock.isBlockBox) {
8088
+ targetBlock = prevBlock;
8089
+ targetBlcokRect = targetBlock.get(0).getBoundingClientRect();
8090
+ left = targetBlcokRect.x - containerRect.x;
8091
+ top = targetBlcokRect.y + targetBlcokRect.height - containerRect.y + (parseInt(targetBlock.computedCSS('margin-bottom'), 10) / 2);
8092
+ }
8093
+ else {
8094
+ dropPosition = 'top';
8095
+ top = targetBlcokRect.y - containerRect.y - (parseInt(editor.container.computedCSS('padding-top'), 10) / 2);
8096
+ }
8097
+ }
8098
+ dropIndication.css({
8099
+ top: `${top}px`,
8100
+ left: `${left}px`,
8101
+ width: `${targetBlcokRect.width}px`,
8102
+ display: 'block',
8103
+ });
8104
+ });
8105
+ // The dragend event is fired when a drag operation ends (by releasing a mouse button or hitting the escape key).
8106
+ editor.container.on('dragend', () => {
8107
+ if (!dropIndication) {
8108
+ return;
8109
+ }
8110
+ dropIndication.remove();
8111
+ dropIndication = null;
8112
+ });
8113
+ // The drop event is fired when an element or text selection is dropped on a valid drop target.
8114
+ editor.container.on('drop', event => {
8115
+ const dragEvent = event;
8116
+ const dataTransfer = dragEvent.dataTransfer;
8117
+ if (!dataTransfer) {
8118
+ return;
8119
+ }
8120
+ if (!dropIndication) {
8121
+ return;
8122
+ }
8123
+ dropIndication.remove();
8124
+ dropIndication = null;
8125
+ // drop a box
8126
+ if (draggedNode && targetBlock && draggedNode.isBox) {
8127
+ if (draggedNode.get(0) === targetBlock.get(0)) {
8128
+ return;
8129
+ }
8130
+ if (dropPosition === 'bottom' && draggedNode.get(0) === targetBlock.next().get(0)) {
8131
+ return;
8132
+ }
8133
+ dragEvent.preventDefault();
8134
+ const draggedBox = getBox(draggedNode);
8135
+ const range = editor.selection.range;
8136
+ if (targetBlock.isBox) {
8137
+ if (dropPosition === 'top') {
8138
+ range.selectBoxStart(targetBlock);
8139
+ }
8140
+ else {
8141
+ range.selectBoxEnd(targetBlock);
8142
+ }
8143
+ }
8144
+ else {
8145
+ range.selectNodeContents(targetBlock);
8146
+ if (dropPosition === 'top') {
8147
+ range.collapseToStart();
8148
+ }
8149
+ else {
8150
+ range.collapseToEnd();
8151
+ }
8152
+ }
8153
+ editor.insertBox(draggedBox.name, draggedBox.value);
8154
+ draggedNode.remove();
8155
+ editor.history.save();
8156
+ }
7478
8157
  });
7479
8158
  };
7480
8159
 
@@ -8194,15 +8873,17 @@ class LinkPopup {
8194
8873
  if (!this.linkNode) {
8195
8874
  return;
8196
8875
  }
8876
+ const position = nodeAndView(this.linkNode);
8877
+ if (position.left < 0 || position.right < 0 || position.top < 0 || position.bottom < 0) {
8878
+ this.container.css('visibility', 'hidden');
8879
+ return;
8880
+ }
8881
+ this.container.css('visibility', '');
8197
8882
  const linkNativeNode = this.linkNode.get(0);
8198
8883
  // Returns a DOMRect object providing information about the size of an element and its position relative to the viewport.
8199
8884
  const linkRect = linkNativeNode.getBoundingClientRect();
8200
8885
  const linkX = linkRect.x + window.scrollX;
8201
8886
  const linkY = linkRect.y + window.scrollY;
8202
- if (linkX < 0 || linkY < 0) {
8203
- this.hide();
8204
- return;
8205
- }
8206
8887
  // link.x + popup.width > window.width
8207
8888
  if (linkRect.x + this.container.width() > window.innerWidth) {
8208
8889
  // link.x + window.scrollX - (popup.width - link.width)
@@ -8264,14 +8945,14 @@ var link = (editor) => {
8264
8945
  const range = editor.selection.range;
8265
8946
  range.setStartAfter(node);
8266
8947
  range.collapseToStart();
8267
- editor.selection.addRangeToNativeSelection();
8948
+ editor.selection.sync();
8268
8949
  editor.history.save();
8269
8950
  },
8270
8951
  onRemove: node => {
8271
8952
  const range = editor.selection.range;
8272
8953
  range.setStartAfter(node);
8273
8954
  range.collapseToStart();
8274
- editor.selection.addRangeToNativeSelection();
8955
+ editor.selection.sync();
8275
8956
  editor.history.save();
8276
8957
  },
8277
8958
  });
@@ -8318,7 +8999,7 @@ var hr = (editor) => {
8318
8999
  const fragment = new Fragment(nativeFragment);
8319
9000
  fragment.find('hr').each(nativeNode => {
8320
9001
  const node = query(nativeNode);
8321
- const box = new Box('hr');
9002
+ const box = getBox('hr');
8322
9003
  node.replaceWith(box.node);
8323
9004
  });
8324
9005
  });
@@ -8342,7 +9023,7 @@ var image = (editor) => {
8342
9023
  const fragment = new Fragment(nativeFragment);
8343
9024
  fragment.find('img').each(nativeNode => {
8344
9025
  const node = query(nativeNode);
8345
- const box = new Box('image');
9026
+ const box = getBox('image');
8346
9027
  const value = node.attr('data-lake-value');
8347
9028
  if (value === '') {
8348
9029
  box.value = {
@@ -8364,6 +9045,39 @@ var image = (editor) => {
8364
9045
  });
8365
9046
  };
8366
9047
 
9048
+ var file = (editor) => {
9049
+ editor.setPluginConfig('file', {
9050
+ requestMethod: 'POST',
9051
+ requestTypes: [
9052
+ 'application/zip',
9053
+ 'application/x-zip-compressed',
9054
+ 'application/vnd.rar',
9055
+ 'image/gif',
9056
+ 'image/jpeg',
9057
+ 'image/png',
9058
+ 'image/svg+xml',
9059
+ 'text/plain',
9060
+ 'text/html',
9061
+ 'application/pdf',
9062
+ 'application/msword',
9063
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
9064
+ 'application/vnd.ms-excel',
9065
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
9066
+ 'application/vnd.ms-powerpoint',
9067
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
9068
+ ],
9069
+ });
9070
+ if (editor.readonly) {
9071
+ return;
9072
+ }
9073
+ editor.command.add('file', {
9074
+ execute: (value) => {
9075
+ editor.insertBox('file', value);
9076
+ editor.history.save();
9077
+ },
9078
+ });
9079
+ };
9080
+
8367
9081
  const langList = [
8368
9082
  'text',
8369
9083
  'c',
@@ -8740,12 +9454,10 @@ function splitBlock(editor, block) {
8740
9454
  editor.selection.splitBlock();
8741
9455
  block = range.getBlocks()[0];
8742
9456
  if (!block) {
8743
- editor.history.save();
8744
9457
  return;
8745
9458
  }
8746
9459
  if (endText === '' && (block.isHeading || block.name === 'blockquote')) {
8747
9460
  editor.selection.setBlocks('<p />');
8748
- editor.history.save();
8749
9461
  return;
8750
9462
  }
8751
9463
  if (block.isList && block.attr('type') === 'checklist') {
@@ -8990,8 +9702,7 @@ var backspaceKey = (editor) => {
8990
9702
  const prevNode = range.getPrevNode();
8991
9703
  if (prevNode.isBox) {
8992
9704
  event.preventDefault();
8993
- range.selectBox(prevNode);
8994
- editor.removeBox();
9705
+ editor.removeBox(prevNode);
8995
9706
  editor.history.save();
8996
9707
  return;
8997
9708
  }
@@ -9110,8 +9821,7 @@ var deleteKey = (editor) => {
9110
9821
  const nextNode = range.getNextNode();
9111
9822
  if (nextNode.isBox) {
9112
9823
  event.preventDefault();
9113
- range.selectBox(nextNode);
9114
- editor.removeBox();
9824
+ editor.removeBox(nextNode);
9115
9825
  editor.history.save();
9116
9826
  return;
9117
9827
  }
@@ -9292,7 +10002,7 @@ var escapeKey = (editor) => {
9292
10002
  event.preventDefault();
9293
10003
  const boxNode = range.commonAncestor.closest('lake-box');
9294
10004
  range.selectBoxEnd(boxNode);
9295
- selection.addRangeToNativeSelection();
10005
+ selection.sync();
9296
10006
  return;
9297
10007
  }
9298
10008
  if (editor.hasFocus) {
@@ -9304,10 +10014,12 @@ var escapeKey = (editor) => {
9304
10014
 
9305
10015
  Editor.box.add(hrBox);
9306
10016
  Editor.box.add(imageBox);
10017
+ Editor.box.add(fileBox);
9307
10018
  Editor.box.add(codeBlockBox);
9308
10019
  Editor.plugin.add(copy);
9309
10020
  Editor.plugin.add(cut);
9310
10021
  Editor.plugin.add(paste);
10022
+ Editor.plugin.add(drop);
9311
10023
  Editor.plugin.add(undo);
9312
10024
  Editor.plugin.add(redo);
9313
10025
  Editor.plugin.add(selectAll);
@@ -9332,6 +10044,7 @@ Editor.plugin.add(formatPainter);
9332
10044
  Editor.plugin.add(link);
9333
10045
  Editor.plugin.add(hr);
9334
10046
  Editor.plugin.add(image);
10047
+ Editor.plugin.add(file);
9335
10048
  Editor.plugin.add(codeBlock);
9336
10049
  Editor.plugin.add(markdown);
9337
10050
  Editor.plugin.add(enterKey);