lakelib 0.1.15 → 0.1.17

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.
Files changed (36) hide show
  1. package/README.md +12 -2
  2. package/dist/lake.css +155 -5
  3. package/dist/lake.min.js +38 -31
  4. package/dist/lake.min.js.map +1 -1
  5. package/lib/lake.css +155 -5
  6. package/lib/lake.js +338 -108
  7. package/lib/lake.js.map +1 -1
  8. package/lib/types/boxes/equation.d.ts +2 -0
  9. package/lib/types/css/index.d.ts +2 -0
  10. package/lib/types/editor.d.ts +4 -5
  11. package/lib/types/i18n/en-US/index.d.ts +5 -0
  12. package/lib/types/i18n/ja/index.d.ts +5 -0
  13. package/lib/types/i18n/ko/index.d.ts +5 -0
  14. package/lib/types/i18n/types.d.ts +28 -0
  15. package/lib/types/i18n/zh-CN/index.d.ts +5 -0
  16. package/lib/types/index.d.ts +1 -1
  17. package/lib/types/models/box.d.ts +1 -2
  18. package/lib/types/models/fragment.d.ts +1 -2
  19. package/lib/types/models/nodes.d.ts +11 -14
  20. package/lib/types/models/range.d.ts +1 -1
  21. package/lib/types/operations/insert-node.d.ts +1 -2
  22. package/lib/types/parsers/html-parser.d.ts +0 -1
  23. package/lib/types/plugins/equation.d.ts +3 -0
  24. package/lib/types/plugins/special-character.d.ts +3 -0
  25. package/lib/types/types/commands.d.ts +20 -0
  26. package/lib/types/types/dropdown.d.ts +2 -1
  27. package/lib/types/types/native.d.ts +0 -23
  28. package/lib/types/ui/commands-popup.d.ts +24 -0
  29. package/lib/types/ui/toolbar.d.ts +1 -2
  30. package/lib/types/utils/from-base64.d.ts +1 -0
  31. package/lib/types/utils/get-box.d.ts +1 -2
  32. package/lib/types/utils/index.d.ts +2 -0
  33. package/lib/types/utils/query.d.ts +1 -2
  34. package/lib/types/utils/to-base64.d.ts +1 -0
  35. package/lib/types/utils/to-node-list.d.ts +1 -2
  36. package/package.json +20 -20
package/lib/lake.js CHANGED
@@ -1,10 +1,8 @@
1
- import { Base64 } from 'js-base64';
2
1
  import EventEmitter from 'eventemitter3';
3
2
  import { i18nObject as i18nObject$1 } from 'typesafe-i18n';
4
- import debounce from 'lodash/debounce';
5
- import isEqual from 'lodash/isEqual';
6
- import md5 from 'blueimp-md5';
7
- import { createKeybindingsHandler } from 'tinykeys';
3
+ import debounce from 'debounce';
4
+ import isEqual from 'fast-deep-equal/es6';
5
+ import { isKeyHotkey } from 'is-hotkey';
8
6
  import 'photoswipe/style.css';
9
7
  import PhotoSwipeLightbox from 'photoswipe/lightbox';
10
8
  import PhotoSwipe from 'photoswipe';
@@ -145,6 +143,10 @@ var attachment = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=
145
143
 
146
144
  var emoji$1 = "<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,216ZM80,108a12,12,0,1,1,12,12A12,12,0,0,1,80,108Zm96,0a12,12,0,1,1-12-12A12,12,0,0,1,176,108Zm-1.07,48c-10.29,17.79-27.4,28-46.93,28s-36.63-10.2-46.92-28a8,8,0,1,1,13.84-8c7.47,12.91,19.21,20,33.08,20s25.61-7.1,33.07-20a8,8,0,0,1,13.86,8Z\"></path></svg>";
147
145
 
146
+ var specialCharacter$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 32 32\"><path d=\"M22.7373,25A14.3093,14.3093,0,0,0,27,15C27,8.42,22.58,4,16,4S5,8.42,5,15A14.3093,14.3093,0,0,0,9.2627,25H4v2h8V24.7617A12.5683,12.5683,0,0,1,7,15c0-5.4673,3.5327-9,9-9s9,3.5327,9,9a12.5683,12.5683,0,0,1-5,9.7617V27h8V25Z\"/></svg>";
147
+
148
+ var equation$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M184,72V56H80.65l53.6,67a8,8,0,0,1,0,10l-53.6,67H184V184a8,8,0,0,1,16,0v24a8,8,0,0,1-8,8H64a8,8,0,0,1-6.25-13l60-75-60-75A8,8,0,0,1,64,40H192a8,8,0,0,1,8,8V72a8,8,0,0,1-16,0Z\"></path></svg>";
149
+
148
150
  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>";
149
151
 
150
152
  // These icons are sourced from open source projects.
@@ -224,6 +226,8 @@ const icons = new Map([
224
226
  ['video', video$1],
225
227
  ['attachment', attachment],
226
228
  ['emoji', emoji$1],
229
+ ['specialCharacter', specialCharacter$1],
230
+ ['equation', equation$1],
227
231
  ['table', table],
228
232
  ]);
229
233
 
@@ -287,6 +291,28 @@ function inString(string, value, delimiter) {
287
291
  return (delimiter + string + delimiter).indexOf(delimiter + value + delimiter) >= 0;
288
292
  }
289
293
 
294
+ // Creates a Base64-encoded ASCII string from a string.
295
+ function toBase64(value) {
296
+ const encoder = new TextEncoder();
297
+ const byteArray = encoder.encode(value);
298
+ let binaryString = '';
299
+ byteArray.forEach(byte => {
300
+ binaryString += String.fromCharCode(byte);
301
+ });
302
+ return window.btoa(binaryString);
303
+ }
304
+
305
+ // Decodes a string of data which has been encoded using Base64 encoding.
306
+ function fromBase64(value) {
307
+ const binaryString = window.atob(value);
308
+ const byteArray = new Uint8Array(binaryString.length);
309
+ for (let i = 0; i < binaryString.length; i++) {
310
+ byteArray[i] = binaryString.charCodeAt(i);
311
+ }
312
+ const decoder = new TextDecoder();
313
+ return decoder.decode(byteArray);
314
+ }
315
+
290
316
  // Converts an alpha value to a hex value.
291
317
  function alphaToHex(value) {
292
318
  const hexString = Math.round(Number.parseFloat(value) * 255).toString(16);
@@ -405,8 +431,6 @@ function toNodeList(content, valueType) {
405
431
  return nodeList;
406
432
  }
407
433
 
408
- const NativeNode = Node;
409
-
410
434
  const blockTagNames = new Set([
411
435
  'h1',
412
436
  'h2',
@@ -515,7 +539,7 @@ class Nodes {
515
539
  return false;
516
540
  }
517
541
  const node = this.get(0);
518
- return node.nodeType === NativeNode.ELEMENT_NODE;
542
+ return node.nodeType === Node.ELEMENT_NODE;
519
543
  }
520
544
  // Returns a boolean value indicating whether the node is a text node.
521
545
  get isText() {
@@ -523,7 +547,7 @@ class Nodes {
523
547
  return false;
524
548
  }
525
549
  const node = this.get(0);
526
- return node.nodeType === NativeNode.TEXT_NODE;
550
+ return node.nodeType === Node.TEXT_NODE;
527
551
  }
528
552
  // Returns a boolean value indicating whether the node is a block element.
529
553
  get isBlock() {
@@ -679,7 +703,7 @@ class Nodes {
679
703
  eachElement(callback) {
680
704
  const nodes = this.getAll();
681
705
  for (let i = 0; i < nodes.length; i++) {
682
- if (nodes[i].nodeType === NativeNode.ELEMENT_NODE) {
706
+ if (nodes[i].nodeType === Node.ELEMENT_NODE) {
683
707
  if (callback(nodes[i], i) === false) {
684
708
  return this;
685
709
  }
@@ -1214,7 +1238,7 @@ class Nodes {
1214
1238
  return `node (${node.lakeId}): ${nodeValue}`;
1215
1239
  }
1216
1240
  // Prints information of the first node.
1217
- debug() {
1241
+ info() {
1218
1242
  debug(this.toString());
1219
1243
  }
1220
1244
  }
@@ -1355,12 +1379,12 @@ class Range {
1355
1379
  reference = 'right';
1356
1380
  }
1357
1381
  }
1358
- else {
1382
+ else if (range.startNode.isText) {
1359
1383
  const text = range.startNode.text();
1360
1384
  if (range.startOffset < text.length) {
1361
1385
  range.setEnd(range.startNode, range.startOffset + 1);
1362
1386
  }
1363
- else {
1387
+ else if (range.startOffset > 0) {
1364
1388
  range.setStart(range.startNode, range.startOffset - 1);
1365
1389
  reference = 'right';
1366
1390
  }
@@ -1813,7 +1837,7 @@ class Range {
1813
1837
  return this.range.cloneContents();
1814
1838
  }
1815
1839
  // Prints information of the range.
1816
- debug() {
1840
+ info() {
1817
1841
  debug('--- range information ---');
1818
1842
  debug('start node:', this.startNode.toString(), ', offset:', this.startOffset);
1819
1843
  debug('end node:', this.endNode.toString(), ', offset:', this.endOffset);
@@ -2851,6 +2875,7 @@ var enUS = {
2851
2875
  image: 'Image',
2852
2876
  file: 'File',
2853
2877
  emoji: 'Emoji',
2878
+ equation: 'Equation',
2854
2879
  removeColor: 'Remove color',
2855
2880
  },
2856
2881
  link: {
@@ -2886,6 +2911,10 @@ var enUS = {
2886
2911
  codeBlock: {
2887
2912
  langType: 'Select language',
2888
2913
  },
2914
+ equation: {
2915
+ save: 'Done',
2916
+ placeholder: 'Type a TeX expression',
2917
+ },
2889
2918
  };
2890
2919
 
2891
2920
  var zhCN = {
@@ -2935,6 +2964,7 @@ var zhCN = {
2935
2964
  image: '图片',
2936
2965
  file: '文件',
2937
2966
  emoji: '表情',
2967
+ equation: '公式',
2938
2968
  removeColor: '默认',
2939
2969
  },
2940
2970
  link: {
@@ -2970,6 +3000,10 @@ var zhCN = {
2970
3000
  codeBlock: {
2971
3001
  langType: '选择代码语言',
2972
3002
  },
3003
+ equation: {
3004
+ save: '确定',
3005
+ placeholder: '请输入 TeX 公式',
3006
+ },
2973
3007
  };
2974
3008
 
2975
3009
  var ja = {
@@ -3019,6 +3053,7 @@ var ja = {
3019
3053
  image: '画像',
3020
3054
  file: 'ファイル',
3021
3055
  emoji: '絵文字',
3056
+ equation: '公式',
3022
3057
  removeColor: 'デフォルト',
3023
3058
  },
3024
3059
  link: {
@@ -3054,6 +3089,10 @@ var ja = {
3054
3089
  codeBlock: {
3055
3090
  langType: 'コード言語を選択',
3056
3091
  },
3092
+ equation: {
3093
+ save: '確認',
3094
+ placeholder: 'TeX 公式を入力してください。',
3095
+ },
3057
3096
  };
3058
3097
 
3059
3098
  var ko = {
@@ -3103,6 +3142,7 @@ var ko = {
3103
3142
  image: '이미지',
3104
3143
  file: '파일',
3105
3144
  emoji: '이모지',
3145
+ equation: '수식',
3106
3146
  removeColor: '기본색',
3107
3147
  },
3108
3148
  link: {
@@ -3138,6 +3178,10 @@ var ko = {
3138
3178
  codeBlock: {
3139
3179
  langType: '코드언어 선택',
3140
3180
  },
3181
+ equation: {
3182
+ save: '확인',
3183
+ placeholder: 'TeX 수식을 입력하세요',
3184
+ },
3141
3185
  };
3142
3186
 
3143
3187
  const localeTranslations = {
@@ -3204,11 +3248,11 @@ class Dropdown {
3204
3248
  if (value === '') {
3205
3249
  return [];
3206
3250
  }
3207
- return JSON.parse(Base64.decode(value));
3251
+ return JSON.parse(fromBase64(value));
3208
3252
  }
3209
3253
  // Updates the value of the node.
3210
3254
  static setValue(node, value) {
3211
- node.attr('value', Base64.encode(JSON.stringify(value)));
3255
+ node.attr('value', toBase64(JSON.stringify(value)));
3212
3256
  }
3213
3257
  static getMenuMap(menuItems, locale) {
3214
3258
  const menuMap = new Map();
@@ -3241,7 +3285,11 @@ class Dropdown {
3241
3285
  `;
3242
3286
  const listNode = query(listContent);
3243
3287
  menuNode.append(listNode);
3244
- if (config.menuType === 'color') {
3288
+ if (config.menuType === 'character') {
3289
+ listNode.attr('title', menuText);
3290
+ listNode.find('.lake-dropdown-menu-text').text(menuItem.value);
3291
+ }
3292
+ else if (config.menuType === 'color') {
3245
3293
  listNode.attr('title', menuText);
3246
3294
  listNode.find('.lake-dropdown-menu-text').css('background-color', menuItem.value);
3247
3295
  }
@@ -3357,7 +3405,11 @@ class Dropdown {
3357
3405
  });
3358
3406
  menuNode.on('click', event => {
3359
3407
  event.preventDefault();
3408
+ event.stopPropagation();
3360
3409
  const listItem = query(event.target).closest('li');
3410
+ if (listItem.length === 0) {
3411
+ return;
3412
+ }
3361
3413
  const value = listItem.attr('value');
3362
3414
  Dropdown.setValue(dropdownNode, [value]);
3363
3415
  if (textNode.length > 0) {
@@ -3403,6 +3455,10 @@ class Dropdown {
3403
3455
  if (config.menuWidth) {
3404
3456
  menuNode.css('width', config.menuWidth);
3405
3457
  }
3458
+ if (config.menuHeight) {
3459
+ menuNode.addClass('lake-dropdown-menu-with-scroll');
3460
+ menuNode.css('height', config.menuHeight);
3461
+ }
3406
3462
  Dropdown.setValue(dropdownNode, [defaultValue]);
3407
3463
  if (textNode.length > 0) {
3408
3464
  const menuMap = Dropdown.getMenuMap(config.menuItems, this.locale);
@@ -3525,7 +3581,7 @@ class Box {
3525
3581
  if (typeof node === 'string') {
3526
3582
  const component = boxes.get(node);
3527
3583
  if (component === undefined) {
3528
- throw new Error(`Box "${node}" has not been defined yet.`);
3584
+ throw new Error(`The box "${node}" has not been defined yet.`);
3529
3585
  }
3530
3586
  const type = encode(component.type);
3531
3587
  const name = encode(component.name);
@@ -3538,7 +3594,7 @@ class Box {
3538
3594
  this.node = query(node);
3539
3595
  const component = boxes.get(this.name);
3540
3596
  if (component === undefined) {
3541
- throw new Error(`Box "${this.name}" has not been defined yet.`);
3597
+ throw new Error(`The box "${this.name}" has not been defined yet.`);
3542
3598
  }
3543
3599
  if (component.value && !this.node.hasAttr('value')) {
3544
3600
  this.value = component.value;
@@ -3569,7 +3625,7 @@ class Box {
3569
3625
  container.removeClass('lake-box-hovered');
3570
3626
  });
3571
3627
  container.on('click', () => {
3572
- debug(`Box "${this.name}" (id = ${this.node.id}) value:`);
3628
+ debug(`The box "${this.name}" (id = ${this.node.id}) value:`);
3573
3629
  debug(this.value);
3574
3630
  });
3575
3631
  if (this.type === 'block' && this.node.isContentEditable) {
@@ -3590,11 +3646,11 @@ class Box {
3590
3646
  if (value === '') {
3591
3647
  return {};
3592
3648
  }
3593
- return JSON.parse(Base64.decode(value));
3649
+ return JSON.parse(fromBase64(value));
3594
3650
  }
3595
3651
  // Sets the value of the box.
3596
3652
  set value(value) {
3597
- this.node.attr('value', Base64.encode(JSON.stringify(value)));
3653
+ this.node.attr('value', toBase64(JSON.stringify(value)));
3598
3654
  }
3599
3655
  updateValue(valueKey, valueValue) {
3600
3656
  const value = this.value;
@@ -3608,7 +3664,7 @@ class Box {
3608
3664
  }
3609
3665
  this.value = value;
3610
3666
  }
3611
- // Returns the editor instance of the box.
3667
+ // Returns an instance of the editor that includes the box.
3612
3668
  getEditor() {
3613
3669
  const container = this.node.closest('div[contenteditable]');
3614
3670
  return container.length > 0 ? editors.get(container.id) : undefined;
@@ -3654,15 +3710,16 @@ class Box {
3654
3710
  if (component === undefined) {
3655
3711
  return;
3656
3712
  }
3713
+ this.event.emit('beforeunmount');
3714
+ this.event.removeAllListeners();
3657
3715
  this.addFramework();
3658
3716
  const content = component.render(this);
3659
3717
  if (content !== undefined) {
3660
3718
  const container = this.getContainer();
3661
- const newContainer = container.clone(false);
3662
- newContainer.append(content);
3663
- morph(container, newContainer);
3719
+ container.empty();
3720
+ container.append(content);
3664
3721
  }
3665
- debug(`Box "${this.name}" (id: ${this.node.id}) rendered`);
3722
+ debug(`The box "${this.name}" (id: ${this.node.id}) rendered`);
3666
3723
  }
3667
3724
  // Destroys a rendered box.
3668
3725
  unmount() {
@@ -3670,7 +3727,7 @@ class Box {
3670
3727
  this.event.emit('beforeunmount');
3671
3728
  this.event.removeAllListeners();
3672
3729
  this.node.empty();
3673
- debug(`Box "${this.name}" (id: ${this.node.id}) unmounted`);
3730
+ debug(`The box "${this.name}" (id: ${this.node.id}) unmounted`);
3674
3731
  }
3675
3732
  // Returns a HTML string of the box.
3676
3733
  getHTML() {
@@ -3918,6 +3975,7 @@ var index = /*#__PURE__*/Object.freeze({
3918
3975
  encode: encode,
3919
3976
  fileSize: fileSize,
3920
3977
  fixNumberedList: fixNumberedList,
3978
+ fromBase64: fromBase64,
3921
3979
  getBox: getBox,
3922
3980
  getCSS: getCSS,
3923
3981
  getDeepest: getDeepest,
@@ -3936,6 +3994,7 @@ var index = /*#__PURE__*/Object.freeze({
3936
3994
  setBlockIndent: setBlockIndent,
3937
3995
  splitNodes: splitNodes,
3938
3996
  template: template,
3997
+ toBase64: toBase64,
3939
3998
  toHex: toHex,
3940
3999
  toNodeList: toNodeList,
3941
4000
  uploadFile: uploadFile,
@@ -4236,13 +4295,7 @@ class HTMLParser {
4236
4295
  }
4237
4296
  return html.trim();
4238
4297
  }
4239
- // Returns the result as node list.
4240
- getNodeList() {
4241
- const html = this.getHTML();
4242
- const body = this.parseHTML(html);
4243
- return body.children();
4244
- }
4245
- // Returns the result as document fragment.
4298
+ // Returns a document fragment.
4246
4299
  getFragment() {
4247
4300
  const html = this.getHTML();
4248
4301
  const body = this.parseHTML(html);
@@ -5080,7 +5133,7 @@ function removeBox(range) {
5080
5133
  return box;
5081
5134
  }
5082
5135
 
5083
- var version = "0.1.15";
5136
+ var version = "0.1.17";
5084
5137
 
5085
5138
  // Returns the attributes of the element as an key-value object.
5086
5139
  function getAttributes(node) {
@@ -5258,7 +5311,7 @@ class Selection {
5258
5311
  insertBox(boxName, boxValue) {
5259
5312
  const box = insertBox(this.range, boxName, boxValue);
5260
5313
  if (!box) {
5261
- throw new Error(`Box "${boxName}" cannot be inserted outside the editor.`);
5314
+ throw new Error(`The box "${boxName}" cannot be inserted outside the editor.`);
5262
5315
  }
5263
5316
  return box;
5264
5317
  }
@@ -5361,7 +5414,7 @@ class History {
5361
5414
  addIdToBoxes(node) {
5362
5415
  node.find('lake-box').each(nativeNode => {
5363
5416
  const boxNode = new Nodes(nativeNode);
5364
- const id = md5(`${boxNode.attr('type')}-${boxNode.attr('name')}-${boxNode.attr('value')}`);
5417
+ const id = `${boxNode.attr('name')}-${boxNode.attr('value')}`;
5365
5418
  boxNode.attr('id', id);
5366
5419
  });
5367
5420
  }
@@ -5381,7 +5434,8 @@ class History {
5381
5434
  },
5382
5435
  afterAttributeUpdated: (attributeName, nativeNode) => {
5383
5436
  const node = new Nodes(nativeNode);
5384
- if (attributeName === 'value' && node.name === 'lake-box') {
5437
+ if (['name', 'value'].indexOf(attributeName) >= 0 && node.name === 'lake-box') {
5438
+ getBox(node).unmount();
5385
5439
  const instanceMap = getInstanceMap(container.id);
5386
5440
  instanceMap.delete(node.id);
5387
5441
  }
@@ -5397,7 +5451,7 @@ class History {
5397
5451
  this.removeIdfromBoxes(otherContainer);
5398
5452
  }
5399
5453
  get canUndo() {
5400
- return this.index > 1 && !!this.list[this.index - 1];
5454
+ return this.index > 1 && !!this.list[this.index - 2];
5401
5455
  }
5402
5456
  get canRedo() {
5403
5457
  return !!this.list[this.index];
@@ -5517,53 +5571,65 @@ class History {
5517
5571
  }
5518
5572
  }
5519
5573
 
5520
- const shortenedTypeMap = new Map([
5521
- ['#', 'shift+#'],
5574
+ const aliasMap = new Map([
5575
+ ['arrow-left', 'left'],
5576
+ ['arrow-right', 'right'],
5577
+ ['arrow-up', 'up'],
5578
+ ['arrow-down', 'down'],
5522
5579
  ]);
5523
5580
  class Keystroke {
5524
5581
  constructor(container) {
5525
5582
  this.keydownEventList = [];
5526
5583
  this.keyupEventList = [];
5527
5584
  this.container = container;
5585
+ this.container.on('keydown', event => {
5586
+ const keyboardEvent = event;
5587
+ for (const item of this.keydownEventList) {
5588
+ if (isKeyHotkey(item.type, keyboardEvent)) {
5589
+ if (item.listener(keyboardEvent) === false) {
5590
+ break;
5591
+ }
5592
+ }
5593
+ }
5594
+ });
5595
+ this.container.on('keyup', event => {
5596
+ const keyboardEvent = event;
5597
+ for (const item of this.keyupEventList) {
5598
+ if (isKeyHotkey(item.type, keyboardEvent)) {
5599
+ if (item.listener(keyboardEvent) === false) {
5600
+ break;
5601
+ }
5602
+ }
5603
+ }
5604
+ });
5528
5605
  }
5529
5606
  normalizeType(type) {
5530
5607
  var _a;
5531
- type = (_a = shortenedTypeMap.get(type)) !== null && _a !== void 0 ? _a : type;
5532
- type = type.replace(/(^|\+|\s)mod(\+|\s|$)/g, '$1$mod$2').
5533
- replace(/shift|control|alt|meta|enter|tab|backspace|delete|space|escape|arrow-left|arrow-right|arrow-up|arrow-down/, (match) => match.charAt(0).toUpperCase() + camelCase(match.substring(1))).
5534
- replace(/(^|\+|\s)([a-z])(\+|\s|$)/g, (match, p1, p2, p3) => `${p1}Key${p2.toUpperCase()}${p3}`);
5608
+ type = (_a = aliasMap.get(type)) !== null && _a !== void 0 ? _a : type;
5535
5609
  return type;
5536
5610
  }
5537
5611
  // Sets a keydown shortcut.
5538
5612
  setKeydown(type, listener) {
5539
5613
  type = this.normalizeType(type);
5540
- const handler = createKeybindingsHandler({
5541
- [type]: event => listener(event),
5542
- });
5543
5614
  this.keydownEventList.push({
5544
5615
  type,
5545
5616
  listener,
5546
5617
  });
5547
- this.container.on('keydown', handler);
5548
5618
  }
5549
5619
  // Sets a keyup shortcut.
5550
5620
  setKeyup(type, listener) {
5551
5621
  type = this.normalizeType(type);
5552
- const handler = createKeybindingsHandler({
5553
- [type]: event => listener(event),
5554
- });
5555
5622
  this.keyupEventList.push({
5556
5623
  type,
5557
5624
  listener,
5558
5625
  });
5559
- this.container.on('keyup', handler);
5560
5626
  }
5561
5627
  // Executes the keydown shortcuts.
5562
5628
  keydown(type) {
5563
5629
  type = this.normalizeType(type);
5564
5630
  for (const item of this.keydownEventList) {
5565
5631
  if (item.type === type) {
5566
- if (item.listener(new Event(type)) === false) {
5632
+ if (item.listener(new KeyboardEvent(type)) === false) {
5567
5633
  break;
5568
5634
  }
5569
5635
  }
@@ -5574,7 +5640,7 @@ class Keystroke {
5574
5640
  type = this.normalizeType(type);
5575
5641
  for (const item of this.keyupEventList) {
5576
5642
  if (item.type === type) {
5577
- if (item.listener(new Event(type)) === false) {
5643
+ if (item.listener(new KeyboardEvent(type)) === false) {
5578
5644
  break;
5579
5645
  }
5580
5646
  }
@@ -5730,9 +5796,7 @@ class Editor {
5730
5796
  box.event.emit('blur');
5731
5797
  });
5732
5798
  }, 50, {
5733
- leading: false,
5734
- trailing: true,
5735
- maxWait: 50,
5799
+ immediate: true,
5736
5800
  });
5737
5801
  // Triggers the statechange event when the current selection of the editor is changed.
5738
5802
  this.emitStateChangeEvent = debounce(() => {
@@ -5776,10 +5840,8 @@ class Editor {
5776
5840
  }
5777
5841
  this.event.emit('statechange', state);
5778
5842
  this.state = state;
5779
- }, 100, {
5780
- leading: false,
5781
- trailing: true,
5782
- maxWait: 100,
5843
+ }, 50, {
5844
+ immediate: false,
5783
5845
  });
5784
5846
  if (!config.root) {
5785
5847
  throw new Error('The root of the config must be specified.');
@@ -5814,7 +5876,7 @@ class Editor {
5814
5876
  // Adds or Removes a placeholder class.
5815
5877
  togglePlaceholderClass(value) {
5816
5878
  value = denormalizeValue(value);
5817
- const className = 'lake-show-placeholder';
5879
+ const className = 'lake-placeholder';
5818
5880
  if (value.replace('<focus />', '') === '<p><br /></p>') {
5819
5881
  this.container.addClass(className);
5820
5882
  }
@@ -5866,6 +5928,18 @@ class Editor {
5866
5928
  this.container.on('compositionend', () => {
5867
5929
  this.isComposing = false;
5868
5930
  });
5931
+ this.container.on('beforeinput', event => {
5932
+ const inputEvent = event;
5933
+ // <p><br /><focus /></p>
5934
+ // When the caret is positioned behind a <br> tag, the input event is triggered twice after inserting a sharp(#) in composition mode.
5935
+ if (this.isComposing && inputEvent.inputType === 'insertText') {
5936
+ inputEvent.preventDefault();
5937
+ this.isComposing = false;
5938
+ this.history.save({
5939
+ inputType: 'insertText',
5940
+ });
5941
+ }
5942
+ });
5869
5943
  this.container.on('input', event => {
5870
5944
  const inputEvent = event;
5871
5945
  // Here setTimeout is necessary because isComposing is not false after ending composition.
@@ -5877,6 +5951,9 @@ class Editor {
5877
5951
  }
5878
5952
  // isComposing is false after ending composition because compositionend event has been emitted.
5879
5953
  if (this.isComposing) {
5954
+ if (inputEvent.inputType === 'insertCompositionText') {
5955
+ this.container.removeClass('lake-placeholder');
5956
+ }
5880
5957
  this.event.emit('input', inputEvent);
5881
5958
  return;
5882
5959
  }
@@ -5959,6 +6036,7 @@ class Editor {
5959
6036
  }
5960
6037
  // Fixes wrong content, especially empty tag.
5961
6038
  fixContent() {
6039
+ const range = this.selection.range;
5962
6040
  let changed = false;
5963
6041
  let children = this.container.children();
5964
6042
  for (const child of children) {
@@ -5971,7 +6049,7 @@ class Editor {
5971
6049
  children = this.container.children();
5972
6050
  if (children.length === 0) {
5973
6051
  this.container.html('<p><br /></p>');
5974
- this.selection.range.shrinkAfter(this.container);
6052
+ range.shrinkAfter(this.container);
5975
6053
  changed = true;
5976
6054
  debug('Content fixed: default paragraph was added');
5977
6055
  }
@@ -5981,11 +6059,23 @@ class Editor {
5981
6059
  const paragraph = query('<p />');
5982
6060
  child.before(paragraph);
5983
6061
  paragraph.append(child);
5984
- this.selection.range.shrinkAfter(paragraph);
6062
+ range.shrinkAfter(paragraph);
5985
6063
  changed = true;
5986
6064
  debug(`Content fixed: void element "${child.name}" was wrapped in paragraph`);
5987
6065
  }
5988
6066
  }
6067
+ // In composition mode (e.g., when a user starts entering a Chinese character using a Pinyin IME),
6068
+ // uncompleted text is inserted if the caret is positioned behind a <br> tag.
6069
+ // To fix this bug, the caret needs to be moved to the front of the <br> tag.
6070
+ if (range.isCollapsed) {
6071
+ const prevNode = range.getPrevNode();
6072
+ const nextNode = range.getNextNode();
6073
+ if (prevNode.name === 'br' && nextNode.length === 0) {
6074
+ range.setStartBefore(prevNode);
6075
+ range.collapseToStart();
6076
+ debug('Range fixed: the caret has been moved to the front of the <br> tag');
6077
+ }
6078
+ }
5989
6079
  return changed;
5990
6080
  }
5991
6081
  // Sets default config for a plugin.
@@ -6033,8 +6123,12 @@ class Editor {
6033
6123
  }
6034
6124
  // Scrolls to the caret or the range of the selection.
6035
6125
  scrollToCaret() {
6126
+ const range = this.selection.range;
6127
+ if (range.isBox) {
6128
+ return;
6129
+ }
6036
6130
  // Creates an artificial caret that is the same size as the caret at the current caret position.
6037
- const rangeRect = this.selection.range.getRect();
6131
+ const rangeRect = range.getRect();
6038
6132
  if (rangeRect.x === 0 || rangeRect.y === 0) {
6039
6133
  return;
6040
6134
  }
@@ -6623,6 +6717,15 @@ const toolbarItems = [
6623
6717
  editor.command.execute(value);
6624
6718
  },
6625
6719
  },
6720
+ {
6721
+ name: 'equation',
6722
+ type: 'button',
6723
+ icon: icons.get('equation'),
6724
+ tooltip: locale => locale.toolbar.equation(),
6725
+ onClick: (editor, value) => {
6726
+ editor.command.execute(value);
6727
+ },
6728
+ },
6626
6729
  {
6627
6730
  name: 'heading',
6628
6731
  type: 'dropdown',
@@ -6838,6 +6941,7 @@ class Toolbar {
6838
6941
  menuType: item.menuType,
6839
6942
  menuItems: item.menuItems,
6840
6943
  menuWidth: item.menuWidth,
6944
+ menuHeight: item.menuHeight,
6841
6945
  tabIndex: -1,
6842
6946
  placement: this.placement === 'top' ? 'bottom' : 'top',
6843
6947
  onSelect: value => {
@@ -6991,8 +7095,10 @@ const hrBox = {
6991
7095
  if (!editor) {
6992
7096
  return;
6993
7097
  }
7098
+ const boxContainer = box.getContainer();
6994
7099
  const rootNode = query('<div class="lake-hr"><hr /></div>');
6995
- box.getContainer().append(rootNode);
7100
+ boxContainer.empty();
7101
+ boxContainer.append(rootNode);
6996
7102
  rootNode.on('click', () => {
6997
7103
  editor.selection.selectBox(box);
6998
7104
  });
@@ -7087,10 +7193,10 @@ const codeBlockBox = {
7087
7193
  return;
7088
7194
  }
7089
7195
  const rootNode = query('<div class="lake-code-block" />');
7090
- const container = box.getContainer();
7091
- container.css('width', `${editor.container.innerWidth() - 2}px`);
7092
- container.empty();
7093
- container.append(rootNode);
7196
+ const boxContainer = box.getContainer();
7197
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7198
+ boxContainer.empty();
7199
+ boxContainer.append(rootNode);
7094
7200
  const codeBlockNativeNode = rootNode.get(0);
7095
7201
  if (!codeBlockNativeNode) {
7096
7202
  return;
@@ -7165,24 +7271,24 @@ const codeBlockBox = {
7165
7271
  defaultValue: langItem ? boxValue.lang : codeBlockConfig.defaultLang,
7166
7272
  tooltip: editor.locale.codeBlock.langType(),
7167
7273
  menuType: 'list',
7274
+ menuHeight: '200px',
7168
7275
  menuItems: langItems.map((item) => ({
7169
7276
  value: item.value,
7170
7277
  text: item.text,
7171
7278
  })),
7172
7279
  onSelect: value => {
7173
- const item = langItemMap.get(value);
7174
- codeEditor.dispatch({
7175
- effects: language.reconfigure(item && item.component ? item.component() : []),
7176
- });
7177
7280
  box.updateValue({
7178
7281
  lang: value,
7179
7282
  });
7283
+ box.unmount();
7284
+ box.render();
7285
+ editor.selection.selectBox(box);
7180
7286
  editor.history.save();
7181
7287
  },
7182
7288
  });
7183
7289
  dropdown.render();
7184
7290
  const resizeListener = () => {
7185
- container.css('width', `${editor.container.innerWidth() - 2}px`);
7291
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7186
7292
  };
7187
7293
  editor.event.on('resize', resizeListener);
7188
7294
  rootNode.on('click', () => {
@@ -7448,7 +7554,7 @@ function openFullScreen(box) {
7448
7554
  lightbox.loadAndOpen(currentIndex);
7449
7555
  }
7450
7556
  // Displays error icon and filename.
7451
- function renderError(rootNode, box) {
7557
+ function renderError$1(rootNode, box) {
7452
7558
  return __awaiter(this, void 0, void 0, function* () {
7453
7559
  const editor = box.getEditor();
7454
7560
  if (!editor) {
@@ -7493,7 +7599,7 @@ function renderUploading(rootNode, box) {
7493
7599
  const value = box.value;
7494
7600
  const imageInfo = yield getImageInfo(value.url);
7495
7601
  if (!imageInfo.width || !imageInfo.height) {
7496
- yield renderError(rootNode, box);
7602
+ yield renderError$1(rootNode, box);
7497
7603
  return;
7498
7604
  }
7499
7605
  const maxWidth = editor.container.innerWidth() - 2;
@@ -7557,7 +7663,7 @@ function renderDone(rootNode, box) {
7557
7663
  return;
7558
7664
  }
7559
7665
  if (!imageInfo.width || !imageInfo.height) {
7560
- yield renderError(rootNode, box);
7666
+ yield renderError$1(rootNode, box);
7561
7667
  return;
7562
7668
  }
7563
7669
  let width = value.width;
@@ -7630,26 +7736,26 @@ const imageBox = {
7630
7736
  box.node.hide();
7631
7737
  return;
7632
7738
  }
7633
- const container = box.getContainer();
7634
- if (value.width && value.height && container.find('.lake-progress').length === 0) {
7635
- container.css({
7739
+ const boxContainer = box.getContainer();
7740
+ if (value.width && value.height && boxContainer.find('.lake-progress').length === 0) {
7741
+ boxContainer.css({
7636
7742
  width: `${value.width}px`,
7637
7743
  height: `${value.height}px`,
7638
7744
  });
7639
- container.empty();
7745
+ boxContainer.empty();
7640
7746
  const placeholderNode = query('<div class="lake-image-placeholder" />');
7641
- container.append(placeholderNode);
7747
+ boxContainer.append(placeholderNode);
7642
7748
  const imageIcon = icons.get('image');
7643
7749
  if (imageIcon) {
7644
7750
  placeholderNode.append(imageIcon);
7645
7751
  }
7646
7752
  }
7647
- if (container.first().length === 0) {
7753
+ if (boxContainer.first().length === 0) {
7648
7754
  // The following code is for unit testing because some test cases need to
7649
7755
  // select the content of the box before it is completely loaded.
7650
7756
  // Example:
7651
7757
  // range.setStart(box.getContainer(), 1);
7652
- container.append('<div />');
7758
+ boxContainer.append('<div />');
7653
7759
  }
7654
7760
  // for test
7655
7761
  if (value.status === 'loading') {
@@ -7662,14 +7768,14 @@ const imageBox = {
7662
7768
  promise = renderUploading(rootNode, box);
7663
7769
  }
7664
7770
  else if (value.status === 'error') {
7665
- promise = renderError(rootNode, box);
7771
+ promise = renderError$1(rootNode, box);
7666
7772
  }
7667
7773
  else {
7668
7774
  promise = renderDone(rootNode, box);
7669
7775
  }
7670
7776
  promise.then(() => {
7671
- container.empty();
7672
- container.append(rootNode);
7777
+ boxContainer.empty();
7778
+ boxContainer.append(rootNode);
7673
7779
  rootNode.find('.lake-button-view').on('click', () => openFullScreen(box));
7674
7780
  if (editor.readonly) {
7675
7781
  rootNode.find('.lake-button-remove').hide();
@@ -7829,12 +7935,12 @@ const videoBox = {
7829
7935
  showVideo(box);
7830
7936
  },
7831
7937
  });
7832
- formNode.find('input[name="url"]').on('keydown', createKeybindingsHandler({
7833
- 'Enter': event => {
7938
+ formNode.find('input[name="url"]').on('keydown', event => {
7939
+ if (isKeyHotkey('enter', event)) {
7834
7940
  event.preventDefault();
7835
7941
  button.node.emit('click');
7836
- },
7837
- }));
7942
+ }
7943
+ });
7838
7944
  button.render();
7839
7945
  rootNode.append(formNode);
7840
7946
  appendButtonGroup(box);
@@ -7919,12 +8025,12 @@ const fileBox = {
7919
8025
  box.node.hide();
7920
8026
  return;
7921
8027
  }
7922
- const container = box.getContainer();
8028
+ const boxContainer = box.getContainer();
7923
8029
  const rootNode = query('<div class="lake-file" />');
7924
8030
  rootNode.addClass(`lake-file-${value.status}`);
7925
8031
  appendContent(rootNode, box);
7926
- container.empty();
7927
- container.append(rootNode);
8032
+ boxContainer.empty();
8033
+ boxContainer.append(rootNode);
7928
8034
  if (!editor.readonly) {
7929
8035
  rootNode.on('click', () => {
7930
8036
  editor.selection.selectBox(box);
@@ -7949,16 +8055,100 @@ const emojiBox = {
7949
8055
  return;
7950
8056
  }
7951
8057
  const value = box.value;
8058
+ const boxContainer = box.getContainer();
7952
8059
  const rootNode = query(safeTemplate `
7953
8060
  <div class="lake-emoji"><img src="${value.url}" title="${value.title}" /></div>
7954
8061
  `);
7955
- box.getContainer().append(rootNode);
8062
+ boxContainer.empty();
8063
+ boxContainer.append(rootNode);
7956
8064
  rootNode.on('click', () => {
7957
8065
  editor.selection.selectBox(box);
7958
8066
  });
7959
8067
  },
7960
8068
  };
7961
8069
 
8070
+ const defaultExpression = String.raw `\sqrt{x}`;
8071
+ function renderError(box) {
8072
+ const editor = box.getEditor();
8073
+ if (!editor) {
8074
+ return;
8075
+ }
8076
+ if (editor.readonly) {
8077
+ box.node.hide();
8078
+ return;
8079
+ }
8080
+ const defaultCode = (box.value.code || '').trim();
8081
+ const rootNode = box.getContainer().find('.lake-equation');
8082
+ rootNode.addClass('lake-equation-error');
8083
+ rootNode.text(defaultCode);
8084
+ rootNode.on('click', () => {
8085
+ editor.selection.selectBox(box);
8086
+ });
8087
+ editor.config.onMessage('warning', `
8088
+ The box "${box.name}" (id: ${box.node.id}) failed to display because window.katex was not found.
8089
+ Please check if the "katex" library is added to this page.
8090
+ `.trim());
8091
+ }
8092
+ const equationBox = {
8093
+ type: 'inline',
8094
+ name: 'equation',
8095
+ render: box => {
8096
+ const editor = box.getEditor();
8097
+ if (!editor) {
8098
+ return;
8099
+ }
8100
+ const rootNode = query('<div class="lake-equation" />');
8101
+ const boxContainer = box.getContainer();
8102
+ boxContainer.empty();
8103
+ boxContainer.append(rootNode);
8104
+ const katex = window.katex;
8105
+ if (!katex) {
8106
+ renderError(box);
8107
+ return;
8108
+ }
8109
+ const defaultCode = (box.value.code || '').trim();
8110
+ const viewNode = query('<div class="lake-equation-view" />');
8111
+ rootNode.append(viewNode);
8112
+ viewNode.html(window.katex.renderToString(defaultCode || defaultExpression, {
8113
+ throwOnError: false,
8114
+ }));
8115
+ viewNode.on('click', () => {
8116
+ editor.selection.selectBox(box);
8117
+ });
8118
+ const formNode = query(safeTemplate `
8119
+ <div class="lake-equation-form">
8120
+ <div class="lake-row">
8121
+ <textarea name="code" placeholder="${editor.locale.equation.placeholder()}"></textarea>
8122
+ </div>
8123
+ <div class="lake-row lake-button-row"></div>
8124
+ </div>
8125
+ `);
8126
+ rootNode.append(formNode);
8127
+ const textareaNode = formNode.find('textarea');
8128
+ const textareaNativeNode = textareaNode.get(0);
8129
+ textareaNativeNode.value = defaultCode;
8130
+ textareaNode.on('input', () => {
8131
+ const code = textareaNativeNode.value.trim();
8132
+ viewNode.html(window.katex.renderToString(code || defaultExpression, {
8133
+ throwOnError: false,
8134
+ }));
8135
+ box.updateValue('code', code);
8136
+ });
8137
+ const button = new Button({
8138
+ root: formNode.find('.lake-button-row'),
8139
+ name: 'save',
8140
+ type: 'primary',
8141
+ text: editor.locale.equation.save(),
8142
+ onClick: () => {
8143
+ editor.selection.range.selectBoxEnd(box.node);
8144
+ editor.selection.sync();
8145
+ editor.history.save();
8146
+ },
8147
+ });
8148
+ button.render();
8149
+ },
8150
+ };
8151
+
7962
8152
  var copy = (editor) => {
7963
8153
  editor.event.on('copy', event => {
7964
8154
  const range = editor.selection.range;
@@ -8393,6 +8583,7 @@ var undo = (editor) => {
8393
8583
  return;
8394
8584
  }
8395
8585
  editor.command.add('undo', {
8586
+ isDisabled: () => !editor.history.canUndo,
8396
8587
  execute: () => {
8397
8588
  editor.history.undo();
8398
8589
  },
@@ -8412,6 +8603,7 @@ var redo = (editor) => {
8412
8603
  return;
8413
8604
  }
8414
8605
  editor.command.add('redo', {
8606
+ isDisabled: () => !editor.history.canRedo,
8415
8607
  execute: () => {
8416
8608
  editor.history.redo();
8417
8609
  },
@@ -9052,18 +9244,18 @@ class LinkPopup {
9052
9244
  },
9053
9245
  });
9054
9246
  button.render();
9055
- this.container.find('input[name="url"]').on('keydown', createKeybindingsHandler({
9056
- 'Enter': event => {
9247
+ this.container.find('input[name="url"]').on('keydown', event => {
9248
+ if (isKeyHotkey('enter', event)) {
9057
9249
  event.preventDefault();
9058
9250
  button.node.emit('click');
9059
- },
9060
- }));
9061
- this.container.find('input[name="title"]').on('keydown', createKeybindingsHandler({
9062
- 'Enter': event => {
9251
+ }
9252
+ });
9253
+ this.container.find('input[name="title"]').on('keydown', event => {
9254
+ if (isKeyHotkey('enter', event)) {
9063
9255
  event.preventDefault();
9064
9256
  button.node.emit('click');
9065
- },
9066
- }));
9257
+ }
9258
+ });
9067
9259
  }
9068
9260
  // Remove link
9069
9261
  appendUnlinkButton() {
@@ -9386,6 +9578,36 @@ var emoji = (editor) => {
9386
9578
  });
9387
9579
  };
9388
9580
 
9581
+ var equation = (editor) => {
9582
+ if (!window.katex) {
9583
+ return;
9584
+ }
9585
+ if (editor.readonly) {
9586
+ return;
9587
+ }
9588
+ editor.command.add('equation', {
9589
+ execute: (value) => {
9590
+ const box = editor.selection.insertBox('equation', value);
9591
+ editor.selection.selectBox(box);
9592
+ editor.history.save();
9593
+ },
9594
+ });
9595
+ };
9596
+
9597
+ var specialCharacter = (editor) => {
9598
+ if (editor.readonly) {
9599
+ return;
9600
+ }
9601
+ editor.command.add('specialCharacter', {
9602
+ execute: (value) => {
9603
+ const fragment = new Fragment();
9604
+ fragment.append(document.createTextNode(value));
9605
+ editor.selection.insertFragment(fragment);
9606
+ editor.history.save();
9607
+ },
9608
+ });
9609
+ };
9610
+
9389
9611
  const headingTypeMap = new Map([
9390
9612
  ['#', 'h1'],
9391
9613
  ['##', 'h2'],
@@ -10008,7 +10230,8 @@ var backspaceKey = (editor) => {
10008
10230
  editor.history.save();
10009
10231
  return;
10010
10232
  }
10011
- if (prevNode.name === 'br' && prevNode.prev().length > 0) {
10233
+ const nextNode = range.getNextNode();
10234
+ if (prevNode.name === 'br' && nextNode.length > 0) {
10012
10235
  event.preventDefault();
10013
10236
  range.setStartBefore(prevNode);
10014
10237
  range.collapseToStart();
@@ -10156,8 +10379,12 @@ var tabKey = (editor) => {
10156
10379
  if (editor.config.indentWithTab === false) {
10157
10380
  return;
10158
10381
  }
10382
+ const range = editor.selection.range;
10383
+ if (range.isInsideBox) {
10384
+ return;
10385
+ }
10159
10386
  event.preventDefault();
10160
- const blocks = editor.selection.range.getBlocks();
10387
+ const blocks = range.getBlocks();
10161
10388
  for (const block of blocks) {
10162
10389
  if (block.name !== 'p' || block.css('text-indent') === '2em') {
10163
10390
  setBlockIndent(block, 'increase');
@@ -10321,6 +10548,7 @@ Editor.box.add(imageBox);
10321
10548
  Editor.box.add(videoBox);
10322
10549
  Editor.box.add(fileBox);
10323
10550
  Editor.box.add(emojiBox);
10551
+ Editor.box.add(equationBox);
10324
10552
  Editor.plugin.add(copy);
10325
10553
  Editor.plugin.add(cut);
10326
10554
  Editor.plugin.add(paste);
@@ -10353,6 +10581,8 @@ Editor.plugin.add(image);
10353
10581
  Editor.plugin.add(video);
10354
10582
  Editor.plugin.add(file);
10355
10583
  Editor.plugin.add(emoji);
10584
+ Editor.plugin.add(equation);
10585
+ Editor.plugin.add(specialCharacter);
10356
10586
  Editor.plugin.add(markdown);
10357
10587
  Editor.plugin.add(enterKey);
10358
10588
  Editor.plugin.add(shiftEnterKey);