lakelib 0.1.16 → 0.1.18

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
@@ -2,7 +2,7 @@ import EventEmitter from 'eventemitter3';
2
2
  import { i18nObject as i18nObject$1 } from 'typesafe-i18n';
3
3
  import debounce from 'debounce';
4
4
  import isEqual from 'fast-deep-equal/es6';
5
- import { createKeybindingsHandler } from 'tinykeys';
5
+ import { isKeyHotkey } from 'is-hotkey';
6
6
  import 'photoswipe/style.css';
7
7
  import PhotoSwipeLightbox from 'photoswipe/lightbox';
8
8
  import PhotoSwipe from 'photoswipe';
@@ -143,7 +143,9 @@ var attachment = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=
143
143
 
144
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>";
145
145
 
146
- var specialCharacter = "<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>";
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>";
147
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
 
@@ -224,7 +226,8 @@ const icons = new Map([
224
226
  ['video', video$1],
225
227
  ['attachment', attachment],
226
228
  ['emoji', emoji$1],
227
- ['specialCharacter', specialCharacter],
229
+ ['specialCharacter', specialCharacter$1],
230
+ ['equation', equation$1],
228
231
  ['table', table],
229
232
  ]);
230
233
 
@@ -428,8 +431,6 @@ function toNodeList(content, valueType) {
428
431
  return nodeList;
429
432
  }
430
433
 
431
- const NativeNode = Node;
432
-
433
434
  const blockTagNames = new Set([
434
435
  'h1',
435
436
  'h2',
@@ -538,7 +539,7 @@ class Nodes {
538
539
  return false;
539
540
  }
540
541
  const node = this.get(0);
541
- return node.nodeType === NativeNode.ELEMENT_NODE;
542
+ return node.nodeType === Node.ELEMENT_NODE;
542
543
  }
543
544
  // Returns a boolean value indicating whether the node is a text node.
544
545
  get isText() {
@@ -546,7 +547,7 @@ class Nodes {
546
547
  return false;
547
548
  }
548
549
  const node = this.get(0);
549
- return node.nodeType === NativeNode.TEXT_NODE;
550
+ return node.nodeType === Node.TEXT_NODE;
550
551
  }
551
552
  // Returns a boolean value indicating whether the node is a block element.
552
553
  get isBlock() {
@@ -662,10 +663,18 @@ class Nodes {
662
663
  }
663
664
  const nodeText = this.text();
664
665
  const isEmptyText = nodeText === '' || /^[\r\n\u200B\u2060]+$/.test(nodeText);
665
- if (this.isElement && isEmptyText) {
666
- return this.find('lake-box').length === 0;
666
+ if (!isEmptyText) {
667
+ return false;
668
+ }
669
+ if (this.isElement) {
670
+ if (this.find('lake-box').length > 0) {
671
+ return false;
672
+ }
673
+ if (this.find('br').length > 1) {
674
+ return false;
675
+ }
667
676
  }
668
- return isEmptyText;
677
+ return true;
669
678
  }
670
679
  // Returns a boolean value indicating whether the node and the target node are siblings.
671
680
  isSibling(target) {
@@ -702,7 +711,7 @@ class Nodes {
702
711
  eachElement(callback) {
703
712
  const nodes = this.getAll();
704
713
  for (let i = 0; i < nodes.length; i++) {
705
- if (nodes[i].nodeType === NativeNode.ELEMENT_NODE) {
714
+ if (nodes[i].nodeType === Node.ELEMENT_NODE) {
706
715
  if (callback(nodes[i], i) === false) {
707
716
  return this;
708
717
  }
@@ -1237,7 +1246,7 @@ class Nodes {
1237
1246
  return `node (${node.lakeId}): ${nodeValue}`;
1238
1247
  }
1239
1248
  // Prints information of the first node.
1240
- debug() {
1249
+ info() {
1241
1250
  debug(this.toString());
1242
1251
  }
1243
1252
  }
@@ -1378,12 +1387,12 @@ class Range {
1378
1387
  reference = 'right';
1379
1388
  }
1380
1389
  }
1381
- else {
1390
+ else if (range.startNode.isText) {
1382
1391
  const text = range.startNode.text();
1383
1392
  if (range.startOffset < text.length) {
1384
1393
  range.setEnd(range.startNode, range.startOffset + 1);
1385
1394
  }
1386
- else {
1395
+ else if (range.startOffset > 0) {
1387
1396
  range.setStart(range.startNode, range.startOffset - 1);
1388
1397
  reference = 'right';
1389
1398
  }
@@ -1836,7 +1845,7 @@ class Range {
1836
1845
  return this.range.cloneContents();
1837
1846
  }
1838
1847
  // Prints information of the range.
1839
- debug() {
1848
+ info() {
1840
1849
  debug('--- range information ---');
1841
1850
  debug('start node:', this.startNode.toString(), ', offset:', this.startOffset);
1842
1851
  debug('end node:', this.endNode.toString(), ', offset:', this.endOffset);
@@ -2874,6 +2883,7 @@ var enUS = {
2874
2883
  image: 'Image',
2875
2884
  file: 'File',
2876
2885
  emoji: 'Emoji',
2886
+ equation: 'Equation',
2877
2887
  removeColor: 'Remove color',
2878
2888
  },
2879
2889
  link: {
@@ -2909,6 +2919,10 @@ var enUS = {
2909
2919
  codeBlock: {
2910
2920
  langType: 'Select language',
2911
2921
  },
2922
+ equation: {
2923
+ save: 'Done',
2924
+ placeholder: 'Type a TeX expression',
2925
+ },
2912
2926
  };
2913
2927
 
2914
2928
  var zhCN = {
@@ -2958,6 +2972,7 @@ var zhCN = {
2958
2972
  image: '图片',
2959
2973
  file: '文件',
2960
2974
  emoji: '表情',
2975
+ equation: '公式',
2961
2976
  removeColor: '默认',
2962
2977
  },
2963
2978
  link: {
@@ -2993,6 +3008,10 @@ var zhCN = {
2993
3008
  codeBlock: {
2994
3009
  langType: '选择代码语言',
2995
3010
  },
3011
+ equation: {
3012
+ save: '确定',
3013
+ placeholder: '请输入 TeX 公式',
3014
+ },
2996
3015
  };
2997
3016
 
2998
3017
  var ja = {
@@ -3042,6 +3061,7 @@ var ja = {
3042
3061
  image: '画像',
3043
3062
  file: 'ファイル',
3044
3063
  emoji: '絵文字',
3064
+ equation: '公式',
3045
3065
  removeColor: 'デフォルト',
3046
3066
  },
3047
3067
  link: {
@@ -3077,6 +3097,10 @@ var ja = {
3077
3097
  codeBlock: {
3078
3098
  langType: 'コード言語を選択',
3079
3099
  },
3100
+ equation: {
3101
+ save: '確認',
3102
+ placeholder: 'TeX 公式を入力してください。',
3103
+ },
3080
3104
  };
3081
3105
 
3082
3106
  var ko = {
@@ -3126,6 +3150,7 @@ var ko = {
3126
3150
  image: '이미지',
3127
3151
  file: '파일',
3128
3152
  emoji: '이모지',
3153
+ equation: '수식',
3129
3154
  removeColor: '기본색',
3130
3155
  },
3131
3156
  link: {
@@ -3161,6 +3186,10 @@ var ko = {
3161
3186
  codeBlock: {
3162
3187
  langType: '코드언어 선택',
3163
3188
  },
3189
+ equation: {
3190
+ save: '확인',
3191
+ placeholder: 'TeX 수식을 입력하세요',
3192
+ },
3164
3193
  };
3165
3194
 
3166
3195
  const localeTranslations = {
@@ -3560,7 +3589,7 @@ class Box {
3560
3589
  if (typeof node === 'string') {
3561
3590
  const component = boxes.get(node);
3562
3591
  if (component === undefined) {
3563
- throw new Error(`Box "${node}" has not been defined yet.`);
3592
+ throw new Error(`The box "${node}" has not been defined yet.`);
3564
3593
  }
3565
3594
  const type = encode(component.type);
3566
3595
  const name = encode(component.name);
@@ -3573,7 +3602,7 @@ class Box {
3573
3602
  this.node = query(node);
3574
3603
  const component = boxes.get(this.name);
3575
3604
  if (component === undefined) {
3576
- throw new Error(`Box "${this.name}" has not been defined yet.`);
3605
+ throw new Error(`The box "${this.name}" has not been defined yet.`);
3577
3606
  }
3578
3607
  if (component.value && !this.node.hasAttr('value')) {
3579
3608
  this.value = component.value;
@@ -3604,7 +3633,7 @@ class Box {
3604
3633
  container.removeClass('lake-box-hovered');
3605
3634
  });
3606
3635
  container.on('click', () => {
3607
- debug(`Box "${this.name}" (id = ${this.node.id}) value:`);
3636
+ debug(`The box "${this.name}" (id = ${this.node.id}) value:`);
3608
3637
  debug(this.value);
3609
3638
  });
3610
3639
  if (this.type === 'block' && this.node.isContentEditable) {
@@ -3643,7 +3672,7 @@ class Box {
3643
3672
  }
3644
3673
  this.value = value;
3645
3674
  }
3646
- // Returns the editor instance of the box.
3675
+ // Returns an instance of the editor that includes the box.
3647
3676
  getEditor() {
3648
3677
  const container = this.node.closest('div[contenteditable]');
3649
3678
  return container.length > 0 ? editors.get(container.id) : undefined;
@@ -3689,15 +3718,16 @@ class Box {
3689
3718
  if (component === undefined) {
3690
3719
  return;
3691
3720
  }
3721
+ this.event.emit('beforeunmount');
3722
+ this.event.removeAllListeners();
3692
3723
  this.addFramework();
3693
3724
  const content = component.render(this);
3694
3725
  if (content !== undefined) {
3695
3726
  const container = this.getContainer();
3696
- const newContainer = container.clone(false);
3697
- newContainer.append(content);
3698
- morph(container, newContainer);
3727
+ container.empty();
3728
+ container.append(content);
3699
3729
  }
3700
- debug(`Box "${this.name}" (id: ${this.node.id}) rendered`);
3730
+ debug(`The box "${this.name}" (id: ${this.node.id}) rendered`);
3701
3731
  }
3702
3732
  // Destroys a rendered box.
3703
3733
  unmount() {
@@ -3705,7 +3735,7 @@ class Box {
3705
3735
  this.event.emit('beforeunmount');
3706
3736
  this.event.removeAllListeners();
3707
3737
  this.node.empty();
3708
- debug(`Box "${this.name}" (id: ${this.node.id}) unmounted`);
3738
+ debug(`The box "${this.name}" (id: ${this.node.id}) unmounted`);
3709
3739
  }
3710
3740
  // Returns a HTML string of the box.
3711
3741
  getHTML() {
@@ -4273,13 +4303,7 @@ class HTMLParser {
4273
4303
  }
4274
4304
  return html.trim();
4275
4305
  }
4276
- // Returns the result as node list.
4277
- getNodeList() {
4278
- const html = this.getHTML();
4279
- const body = this.parseHTML(html);
4280
- return body.children();
4281
- }
4282
- // Returns the result as document fragment.
4306
+ // Returns a document fragment.
4283
4307
  getFragment() {
4284
4308
  const html = this.getHTML();
4285
4309
  const body = this.parseHTML(html);
@@ -5117,7 +5141,7 @@ function removeBox(range) {
5117
5141
  return box;
5118
5142
  }
5119
5143
 
5120
- var version = "0.1.16";
5144
+ var version = "0.1.18";
5121
5145
 
5122
5146
  // Returns the attributes of the element as an key-value object.
5123
5147
  function getAttributes(node) {
@@ -5295,7 +5319,7 @@ class Selection {
5295
5319
  insertBox(boxName, boxValue) {
5296
5320
  const box = insertBox(this.range, boxName, boxValue);
5297
5321
  if (!box) {
5298
- throw new Error(`Box "${boxName}" cannot be inserted outside the editor.`);
5322
+ throw new Error(`The box "${boxName}" cannot be inserted outside the editor.`);
5299
5323
  }
5300
5324
  return box;
5301
5325
  }
@@ -5418,7 +5442,8 @@ class History {
5418
5442
  },
5419
5443
  afterAttributeUpdated: (attributeName, nativeNode) => {
5420
5444
  const node = new Nodes(nativeNode);
5421
- if (attributeName === 'value' && node.name === 'lake-box') {
5445
+ if (['name', 'value'].indexOf(attributeName) >= 0 && node.name === 'lake-box') {
5446
+ getBox(node).unmount();
5422
5447
  const instanceMap = getInstanceMap(container.id);
5423
5448
  instanceMap.delete(node.id);
5424
5449
  }
@@ -5434,7 +5459,7 @@ class History {
5434
5459
  this.removeIdfromBoxes(otherContainer);
5435
5460
  }
5436
5461
  get canUndo() {
5437
- return this.index > 1 && !!this.list[this.index - 1];
5462
+ return this.index > 1 && !!this.list[this.index - 2];
5438
5463
  }
5439
5464
  get canRedo() {
5440
5465
  return !!this.list[this.index];
@@ -5554,53 +5579,71 @@ class History {
5554
5579
  }
5555
5580
  }
5556
5581
 
5557
- const shortenedTypeMap = new Map([
5558
- ['#', 'shift+#'],
5582
+ const aliasMap = new Map([
5583
+ ['arrow-left', 'left'],
5584
+ ['arrow-right', 'right'],
5585
+ ['arrow-up', 'up'],
5586
+ ['arrow-down', 'down'],
5559
5587
  ]);
5560
5588
  class Keystroke {
5561
5589
  constructor(container) {
5562
5590
  this.keydownEventList = [];
5563
5591
  this.keyupEventList = [];
5564
5592
  this.container = container;
5593
+ this.container.on('keydown', event => {
5594
+ const keyboardEvent = event;
5595
+ if (keyboardEvent.isComposing) {
5596
+ return;
5597
+ }
5598
+ for (const item of this.keydownEventList) {
5599
+ if (isKeyHotkey(item.type, keyboardEvent)) {
5600
+ if (item.listener(keyboardEvent) === false) {
5601
+ break;
5602
+ }
5603
+ }
5604
+ }
5605
+ });
5606
+ this.container.on('keyup', event => {
5607
+ const keyboardEvent = event;
5608
+ if (keyboardEvent.isComposing) {
5609
+ return;
5610
+ }
5611
+ for (const item of this.keyupEventList) {
5612
+ if (isKeyHotkey(item.type, keyboardEvent)) {
5613
+ if (item.listener(keyboardEvent) === false) {
5614
+ break;
5615
+ }
5616
+ }
5617
+ }
5618
+ });
5565
5619
  }
5566
5620
  normalizeType(type) {
5567
5621
  var _a;
5568
- type = (_a = shortenedTypeMap.get(type)) !== null && _a !== void 0 ? _a : type;
5569
- type = type.replace(/(^|\+|\s)mod(\+|\s|$)/g, '$1$mod$2').
5570
- 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))).
5571
- replace(/(^|\+|\s)([a-z])(\+|\s|$)/g, (match, p1, p2, p3) => `${p1}Key${p2.toUpperCase()}${p3}`);
5622
+ type = (_a = aliasMap.get(type)) !== null && _a !== void 0 ? _a : type;
5572
5623
  return type;
5573
5624
  }
5574
5625
  // Sets a keydown shortcut.
5575
5626
  setKeydown(type, listener) {
5576
5627
  type = this.normalizeType(type);
5577
- const handler = createKeybindingsHandler({
5578
- [type]: event => listener(event),
5579
- });
5580
5628
  this.keydownEventList.push({
5581
5629
  type,
5582
5630
  listener,
5583
5631
  });
5584
- this.container.on('keydown', handler);
5585
5632
  }
5586
5633
  // Sets a keyup shortcut.
5587
5634
  setKeyup(type, listener) {
5588
5635
  type = this.normalizeType(type);
5589
- const handler = createKeybindingsHandler({
5590
- [type]: event => listener(event),
5591
- });
5592
5636
  this.keyupEventList.push({
5593
5637
  type,
5594
5638
  listener,
5595
5639
  });
5596
- this.container.on('keyup', handler);
5597
5640
  }
5598
5641
  // Executes the keydown shortcuts.
5599
5642
  keydown(type) {
5600
5643
  type = this.normalizeType(type);
5601
5644
  for (const item of this.keydownEventList) {
5602
5645
  if (item.type === type) {
5603
- if (item.listener(new Event(type)) === false) {
5646
+ if (item.listener(new KeyboardEvent(type)) === false) {
5604
5647
  break;
5605
5648
  }
5606
5649
  }
@@ -5611,7 +5654,7 @@ class Keystroke {
5611
5654
  type = this.normalizeType(type);
5612
5655
  for (const item of this.keyupEventList) {
5613
5656
  if (item.type === type) {
5614
- if (item.listener(new Event(type)) === false) {
5657
+ if (item.listener(new KeyboardEvent(type)) === false) {
5615
5658
  break;
5616
5659
  }
5617
5660
  }
@@ -5847,7 +5890,7 @@ class Editor {
5847
5890
  // Adds or Removes a placeholder class.
5848
5891
  togglePlaceholderClass(value) {
5849
5892
  value = denormalizeValue(value);
5850
- const className = 'lake-show-placeholder';
5893
+ const className = 'lake-placeholder';
5851
5894
  if (value.replace('<focus />', '') === '<p><br /></p>') {
5852
5895
  this.container.addClass(className);
5853
5896
  }
@@ -5899,6 +5942,18 @@ class Editor {
5899
5942
  this.container.on('compositionend', () => {
5900
5943
  this.isComposing = false;
5901
5944
  });
5945
+ this.container.on('beforeinput', event => {
5946
+ const inputEvent = event;
5947
+ // <p><br /><focus /></p>
5948
+ // When the caret is positioned behind a <br> tag, the input event is triggered twice after inserting a sharp(#) in composition mode.
5949
+ if (this.isComposing && inputEvent.inputType === 'insertText') {
5950
+ inputEvent.preventDefault();
5951
+ this.isComposing = false;
5952
+ this.history.save({
5953
+ inputType: 'insertText',
5954
+ });
5955
+ }
5956
+ });
5902
5957
  this.container.on('input', event => {
5903
5958
  const inputEvent = event;
5904
5959
  // Here setTimeout is necessary because isComposing is not false after ending composition.
@@ -5910,6 +5965,9 @@ class Editor {
5910
5965
  }
5911
5966
  // isComposing is false after ending composition because compositionend event has been emitted.
5912
5967
  if (this.isComposing) {
5968
+ if (inputEvent.inputType === 'insertCompositionText') {
5969
+ this.container.removeClass('lake-placeholder');
5970
+ }
5913
5971
  this.event.emit('input', inputEvent);
5914
5972
  return;
5915
5973
  }
@@ -5992,6 +6050,7 @@ class Editor {
5992
6050
  }
5993
6051
  // Fixes wrong content, especially empty tag.
5994
6052
  fixContent() {
6053
+ const range = this.selection.range;
5995
6054
  let changed = false;
5996
6055
  let children = this.container.children();
5997
6056
  for (const child of children) {
@@ -6004,7 +6063,7 @@ class Editor {
6004
6063
  children = this.container.children();
6005
6064
  if (children.length === 0) {
6006
6065
  this.container.html('<p><br /></p>');
6007
- this.selection.range.shrinkAfter(this.container);
6066
+ range.shrinkAfter(this.container);
6008
6067
  changed = true;
6009
6068
  debug('Content fixed: default paragraph was added');
6010
6069
  }
@@ -6014,11 +6073,23 @@ class Editor {
6014
6073
  const paragraph = query('<p />');
6015
6074
  child.before(paragraph);
6016
6075
  paragraph.append(child);
6017
- this.selection.range.shrinkAfter(paragraph);
6076
+ range.shrinkAfter(paragraph);
6018
6077
  changed = true;
6019
6078
  debug(`Content fixed: void element "${child.name}" was wrapped in paragraph`);
6020
6079
  }
6021
6080
  }
6081
+ // In composition mode (e.g., when a user starts entering a Chinese character using a Pinyin IME),
6082
+ // uncompleted text is inserted if the caret is positioned behind a <br> tag.
6083
+ // To fix this bug, the caret needs to be moved to the front of the <br> tag.
6084
+ if (range.isCollapsed) {
6085
+ const prevNode = range.getPrevNode();
6086
+ const nextNode = range.getNextNode();
6087
+ if (prevNode.name === 'br' && nextNode.length === 0) {
6088
+ range.setStartBefore(prevNode);
6089
+ range.collapseToStart();
6090
+ debug('Range fixed: the caret has been moved to the front of the <br> tag');
6091
+ }
6092
+ }
6022
6093
  return changed;
6023
6094
  }
6024
6095
  // Sets default config for a plugin.
@@ -6660,6 +6731,15 @@ const toolbarItems = [
6660
6731
  editor.command.execute(value);
6661
6732
  },
6662
6733
  },
6734
+ {
6735
+ name: 'equation',
6736
+ type: 'button',
6737
+ icon: icons.get('equation'),
6738
+ tooltip: locale => locale.toolbar.equation(),
6739
+ onClick: (editor, value) => {
6740
+ editor.command.execute(value);
6741
+ },
6742
+ },
6663
6743
  {
6664
6744
  name: 'heading',
6665
6745
  type: 'dropdown',
@@ -7029,8 +7109,10 @@ const hrBox = {
7029
7109
  if (!editor) {
7030
7110
  return;
7031
7111
  }
7112
+ const boxContainer = box.getContainer();
7032
7113
  const rootNode = query('<div class="lake-hr"><hr /></div>');
7033
- box.getContainer().append(rootNode);
7114
+ boxContainer.empty();
7115
+ boxContainer.append(rootNode);
7034
7116
  rootNode.on('click', () => {
7035
7117
  editor.selection.selectBox(box);
7036
7118
  });
@@ -7125,10 +7207,10 @@ const codeBlockBox = {
7125
7207
  return;
7126
7208
  }
7127
7209
  const rootNode = query('<div class="lake-code-block" />');
7128
- const container = box.getContainer();
7129
- container.css('width', `${editor.container.innerWidth() - 2}px`);
7130
- container.empty();
7131
- container.append(rootNode);
7210
+ const boxContainer = box.getContainer();
7211
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7212
+ boxContainer.empty();
7213
+ boxContainer.append(rootNode);
7132
7214
  const codeBlockNativeNode = rootNode.get(0);
7133
7215
  if (!codeBlockNativeNode) {
7134
7216
  return;
@@ -7220,7 +7302,7 @@ const codeBlockBox = {
7220
7302
  });
7221
7303
  dropdown.render();
7222
7304
  const resizeListener = () => {
7223
- container.css('width', `${editor.container.innerWidth() - 2}px`);
7305
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7224
7306
  };
7225
7307
  editor.event.on('resize', resizeListener);
7226
7308
  rootNode.on('click', () => {
@@ -7486,7 +7568,7 @@ function openFullScreen(box) {
7486
7568
  lightbox.loadAndOpen(currentIndex);
7487
7569
  }
7488
7570
  // Displays error icon and filename.
7489
- function renderError(rootNode, box) {
7571
+ function renderError$1(rootNode, box) {
7490
7572
  return __awaiter(this, void 0, void 0, function* () {
7491
7573
  const editor = box.getEditor();
7492
7574
  if (!editor) {
@@ -7531,7 +7613,7 @@ function renderUploading(rootNode, box) {
7531
7613
  const value = box.value;
7532
7614
  const imageInfo = yield getImageInfo(value.url);
7533
7615
  if (!imageInfo.width || !imageInfo.height) {
7534
- yield renderError(rootNode, box);
7616
+ yield renderError$1(rootNode, box);
7535
7617
  return;
7536
7618
  }
7537
7619
  const maxWidth = editor.container.innerWidth() - 2;
@@ -7595,7 +7677,7 @@ function renderDone(rootNode, box) {
7595
7677
  return;
7596
7678
  }
7597
7679
  if (!imageInfo.width || !imageInfo.height) {
7598
- yield renderError(rootNode, box);
7680
+ yield renderError$1(rootNode, box);
7599
7681
  return;
7600
7682
  }
7601
7683
  let width = value.width;
@@ -7668,26 +7750,26 @@ const imageBox = {
7668
7750
  box.node.hide();
7669
7751
  return;
7670
7752
  }
7671
- const container = box.getContainer();
7672
- if (value.width && value.height && container.find('.lake-progress').length === 0) {
7673
- container.css({
7753
+ const boxContainer = box.getContainer();
7754
+ if (value.width && value.height && boxContainer.find('.lake-progress').length === 0) {
7755
+ boxContainer.css({
7674
7756
  width: `${value.width}px`,
7675
7757
  height: `${value.height}px`,
7676
7758
  });
7677
- container.empty();
7759
+ boxContainer.empty();
7678
7760
  const placeholderNode = query('<div class="lake-image-placeholder" />');
7679
- container.append(placeholderNode);
7761
+ boxContainer.append(placeholderNode);
7680
7762
  const imageIcon = icons.get('image');
7681
7763
  if (imageIcon) {
7682
7764
  placeholderNode.append(imageIcon);
7683
7765
  }
7684
7766
  }
7685
- if (container.first().length === 0) {
7767
+ if (boxContainer.first().length === 0) {
7686
7768
  // The following code is for unit testing because some test cases need to
7687
7769
  // select the content of the box before it is completely loaded.
7688
7770
  // Example:
7689
7771
  // range.setStart(box.getContainer(), 1);
7690
- container.append('<div />');
7772
+ boxContainer.append('<div />');
7691
7773
  }
7692
7774
  // for test
7693
7775
  if (value.status === 'loading') {
@@ -7700,14 +7782,14 @@ const imageBox = {
7700
7782
  promise = renderUploading(rootNode, box);
7701
7783
  }
7702
7784
  else if (value.status === 'error') {
7703
- promise = renderError(rootNode, box);
7785
+ promise = renderError$1(rootNode, box);
7704
7786
  }
7705
7787
  else {
7706
7788
  promise = renderDone(rootNode, box);
7707
7789
  }
7708
7790
  promise.then(() => {
7709
- container.empty();
7710
- container.append(rootNode);
7791
+ boxContainer.empty();
7792
+ boxContainer.append(rootNode);
7711
7793
  rootNode.find('.lake-button-view').on('click', () => openFullScreen(box));
7712
7794
  if (editor.readonly) {
7713
7795
  rootNode.find('.lake-button-remove').hide();
@@ -7867,12 +7949,12 @@ const videoBox = {
7867
7949
  showVideo(box);
7868
7950
  },
7869
7951
  });
7870
- formNode.find('input[name="url"]').on('keydown', createKeybindingsHandler({
7871
- 'Enter': event => {
7952
+ formNode.find('input[name="url"]').on('keydown', event => {
7953
+ if (isKeyHotkey('enter', event)) {
7872
7954
  event.preventDefault();
7873
7955
  button.node.emit('click');
7874
- },
7875
- }));
7956
+ }
7957
+ });
7876
7958
  button.render();
7877
7959
  rootNode.append(formNode);
7878
7960
  appendButtonGroup(box);
@@ -7957,12 +8039,12 @@ const fileBox = {
7957
8039
  box.node.hide();
7958
8040
  return;
7959
8041
  }
7960
- const container = box.getContainer();
8042
+ const boxContainer = box.getContainer();
7961
8043
  const rootNode = query('<div class="lake-file" />');
7962
8044
  rootNode.addClass(`lake-file-${value.status}`);
7963
8045
  appendContent(rootNode, box);
7964
- container.empty();
7965
- container.append(rootNode);
8046
+ boxContainer.empty();
8047
+ boxContainer.append(rootNode);
7966
8048
  if (!editor.readonly) {
7967
8049
  rootNode.on('click', () => {
7968
8050
  editor.selection.selectBox(box);
@@ -7987,16 +8069,100 @@ const emojiBox = {
7987
8069
  return;
7988
8070
  }
7989
8071
  const value = box.value;
8072
+ const boxContainer = box.getContainer();
7990
8073
  const rootNode = query(safeTemplate `
7991
8074
  <div class="lake-emoji"><img src="${value.url}" title="${value.title}" /></div>
7992
8075
  `);
7993
- box.getContainer().append(rootNode);
8076
+ boxContainer.empty();
8077
+ boxContainer.append(rootNode);
7994
8078
  rootNode.on('click', () => {
7995
8079
  editor.selection.selectBox(box);
7996
8080
  });
7997
8081
  },
7998
8082
  };
7999
8083
 
8084
+ const defaultExpression = String.raw `\sqrt{x}`;
8085
+ function renderError(box) {
8086
+ const editor = box.getEditor();
8087
+ if (!editor) {
8088
+ return;
8089
+ }
8090
+ if (editor.readonly) {
8091
+ box.node.hide();
8092
+ return;
8093
+ }
8094
+ const defaultCode = (box.value.code || '').trim();
8095
+ const rootNode = box.getContainer().find('.lake-equation');
8096
+ rootNode.addClass('lake-equation-error');
8097
+ rootNode.text(defaultCode);
8098
+ rootNode.on('click', () => {
8099
+ editor.selection.selectBox(box);
8100
+ });
8101
+ editor.config.onMessage('warning', `
8102
+ The box "${box.name}" (id: ${box.node.id}) failed to display because window.katex was not found.
8103
+ Please check if the "katex" library is added to this page.
8104
+ `.trim());
8105
+ }
8106
+ const equationBox = {
8107
+ type: 'inline',
8108
+ name: 'equation',
8109
+ render: box => {
8110
+ const editor = box.getEditor();
8111
+ if (!editor) {
8112
+ return;
8113
+ }
8114
+ const rootNode = query('<div class="lake-equation" />');
8115
+ const boxContainer = box.getContainer();
8116
+ boxContainer.empty();
8117
+ boxContainer.append(rootNode);
8118
+ const katex = window.katex;
8119
+ if (!katex) {
8120
+ renderError(box);
8121
+ return;
8122
+ }
8123
+ const defaultCode = (box.value.code || '').trim();
8124
+ const viewNode = query('<div class="lake-equation-view" />');
8125
+ rootNode.append(viewNode);
8126
+ viewNode.html(window.katex.renderToString(defaultCode || defaultExpression, {
8127
+ throwOnError: false,
8128
+ }));
8129
+ viewNode.on('click', () => {
8130
+ editor.selection.selectBox(box);
8131
+ });
8132
+ const formNode = query(safeTemplate `
8133
+ <div class="lake-equation-form">
8134
+ <div class="lake-row">
8135
+ <textarea name="code" placeholder="${editor.locale.equation.placeholder()}"></textarea>
8136
+ </div>
8137
+ <div class="lake-row lake-button-row"></div>
8138
+ </div>
8139
+ `);
8140
+ rootNode.append(formNode);
8141
+ const textareaNode = formNode.find('textarea');
8142
+ const textareaNativeNode = textareaNode.get(0);
8143
+ textareaNativeNode.value = defaultCode;
8144
+ textareaNode.on('input', () => {
8145
+ const code = textareaNativeNode.value.trim();
8146
+ viewNode.html(window.katex.renderToString(code || defaultExpression, {
8147
+ throwOnError: false,
8148
+ }));
8149
+ box.updateValue('code', code);
8150
+ });
8151
+ const button = new Button({
8152
+ root: formNode.find('.lake-button-row'),
8153
+ name: 'save',
8154
+ type: 'primary',
8155
+ text: editor.locale.equation.save(),
8156
+ onClick: () => {
8157
+ editor.selection.range.selectBoxEnd(box.node);
8158
+ editor.selection.sync();
8159
+ editor.history.save();
8160
+ },
8161
+ });
8162
+ button.render();
8163
+ },
8164
+ };
8165
+
8000
8166
  var copy = (editor) => {
8001
8167
  editor.event.on('copy', event => {
8002
8168
  const range = editor.selection.range;
@@ -8431,6 +8597,7 @@ var undo = (editor) => {
8431
8597
  return;
8432
8598
  }
8433
8599
  editor.command.add('undo', {
8600
+ isDisabled: () => !editor.history.canUndo,
8434
8601
  execute: () => {
8435
8602
  editor.history.undo();
8436
8603
  },
@@ -8450,6 +8617,7 @@ var redo = (editor) => {
8450
8617
  return;
8451
8618
  }
8452
8619
  editor.command.add('redo', {
8620
+ isDisabled: () => !editor.history.canRedo,
8453
8621
  execute: () => {
8454
8622
  editor.history.redo();
8455
8623
  },
@@ -9090,18 +9258,18 @@ class LinkPopup {
9090
9258
  },
9091
9259
  });
9092
9260
  button.render();
9093
- this.container.find('input[name="url"]').on('keydown', createKeybindingsHandler({
9094
- 'Enter': event => {
9261
+ this.container.find('input[name="url"]').on('keydown', event => {
9262
+ if (isKeyHotkey('enter', event)) {
9095
9263
  event.preventDefault();
9096
9264
  button.node.emit('click');
9097
- },
9098
- }));
9099
- this.container.find('input[name="title"]').on('keydown', createKeybindingsHandler({
9100
- 'Enter': event => {
9265
+ }
9266
+ });
9267
+ this.container.find('input[name="title"]').on('keydown', event => {
9268
+ if (isKeyHotkey('enter', event)) {
9101
9269
  event.preventDefault();
9102
9270
  button.node.emit('click');
9103
- },
9104
- }));
9271
+ }
9272
+ });
9105
9273
  }
9106
9274
  // Remove link
9107
9275
  appendUnlinkButton() {
@@ -9424,6 +9592,36 @@ var emoji = (editor) => {
9424
9592
  });
9425
9593
  };
9426
9594
 
9595
+ var equation = (editor) => {
9596
+ if (!window.katex) {
9597
+ return;
9598
+ }
9599
+ if (editor.readonly) {
9600
+ return;
9601
+ }
9602
+ editor.command.add('equation', {
9603
+ execute: (value) => {
9604
+ const box = editor.selection.insertBox('equation', value);
9605
+ editor.selection.selectBox(box);
9606
+ editor.history.save();
9607
+ },
9608
+ });
9609
+ };
9610
+
9611
+ var specialCharacter = (editor) => {
9612
+ if (editor.readonly) {
9613
+ return;
9614
+ }
9615
+ editor.command.add('specialCharacter', {
9616
+ execute: (value) => {
9617
+ const fragment = new Fragment();
9618
+ fragment.append(document.createTextNode(value));
9619
+ editor.selection.insertFragment(fragment);
9620
+ editor.history.save();
9621
+ },
9622
+ });
9623
+ };
9624
+
9427
9625
  const headingTypeMap = new Map([
9428
9626
  ['#', 'h1'],
9429
9627
  ['##', 'h2'],
@@ -9955,6 +10153,12 @@ function mergeWithPreviousBlock(editor, block) {
9955
10153
  return;
9956
10154
  }
9957
10155
  removeEmptyMarks(range);
10156
+ const nextNode = range.getNextNode();
10157
+ if (nextNode.name === 'br' && prevBlock.name !== 'p') {
10158
+ nextNode.remove();
10159
+ range.shrinkAfter(prevBlock);
10160
+ return;
10161
+ }
9958
10162
  const bookmark = editor.selection.insertBookmark();
9959
10163
  mergeNodes(prevBlock, block);
9960
10164
  editor.selection.toBookmark(bookmark);
@@ -10046,7 +10250,8 @@ var backspaceKey = (editor) => {
10046
10250
  editor.history.save();
10047
10251
  return;
10048
10252
  }
10049
- if (prevNode.name === 'br' && prevNode.prev().length > 0) {
10253
+ const nextNode = range.getNextNode();
10254
+ if (prevNode.name === 'br' && nextNode.length > 0) {
10050
10255
  event.preventDefault();
10051
10256
  range.setStartBefore(prevNode);
10052
10257
  range.collapseToStart();
@@ -10194,8 +10399,12 @@ var tabKey = (editor) => {
10194
10399
  if (editor.config.indentWithTab === false) {
10195
10400
  return;
10196
10401
  }
10402
+ const range = editor.selection.range;
10403
+ if (range.isInsideBox) {
10404
+ return;
10405
+ }
10197
10406
  event.preventDefault();
10198
- const blocks = editor.selection.range.getBlocks();
10407
+ const blocks = range.getBlocks();
10199
10408
  for (const block of blocks) {
10200
10409
  if (block.name !== 'p' || block.css('text-indent') === '2em') {
10201
10410
  setBlockIndent(block, 'increase');
@@ -10359,6 +10568,7 @@ Editor.box.add(imageBox);
10359
10568
  Editor.box.add(videoBox);
10360
10569
  Editor.box.add(fileBox);
10361
10570
  Editor.box.add(emojiBox);
10571
+ Editor.box.add(equationBox);
10362
10572
  Editor.plugin.add(copy);
10363
10573
  Editor.plugin.add(cut);
10364
10574
  Editor.plugin.add(paste);
@@ -10391,6 +10601,8 @@ Editor.plugin.add(image);
10391
10601
  Editor.plugin.add(video);
10392
10602
  Editor.plugin.add(file);
10393
10603
  Editor.plugin.add(emoji);
10604
+ Editor.plugin.add(equation);
10605
+ Editor.plugin.add(specialCharacter);
10394
10606
  Editor.plugin.add(markdown);
10395
10607
  Editor.plugin.add(enterKey);
10396
10608
  Editor.plugin.add(shiftEnterKey);