lakelib 0.1.16 → 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.
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() {
@@ -702,7 +703,7 @@ class Nodes {
702
703
  eachElement(callback) {
703
704
  const nodes = this.getAll();
704
705
  for (let i = 0; i < nodes.length; i++) {
705
- if (nodes[i].nodeType === NativeNode.ELEMENT_NODE) {
706
+ if (nodes[i].nodeType === Node.ELEMENT_NODE) {
706
707
  if (callback(nodes[i], i) === false) {
707
708
  return this;
708
709
  }
@@ -1237,7 +1238,7 @@ class Nodes {
1237
1238
  return `node (${node.lakeId}): ${nodeValue}`;
1238
1239
  }
1239
1240
  // Prints information of the first node.
1240
- debug() {
1241
+ info() {
1241
1242
  debug(this.toString());
1242
1243
  }
1243
1244
  }
@@ -1378,12 +1379,12 @@ class Range {
1378
1379
  reference = 'right';
1379
1380
  }
1380
1381
  }
1381
- else {
1382
+ else if (range.startNode.isText) {
1382
1383
  const text = range.startNode.text();
1383
1384
  if (range.startOffset < text.length) {
1384
1385
  range.setEnd(range.startNode, range.startOffset + 1);
1385
1386
  }
1386
- else {
1387
+ else if (range.startOffset > 0) {
1387
1388
  range.setStart(range.startNode, range.startOffset - 1);
1388
1389
  reference = 'right';
1389
1390
  }
@@ -1836,7 +1837,7 @@ class Range {
1836
1837
  return this.range.cloneContents();
1837
1838
  }
1838
1839
  // Prints information of the range.
1839
- debug() {
1840
+ info() {
1840
1841
  debug('--- range information ---');
1841
1842
  debug('start node:', this.startNode.toString(), ', offset:', this.startOffset);
1842
1843
  debug('end node:', this.endNode.toString(), ', offset:', this.endOffset);
@@ -2874,6 +2875,7 @@ var enUS = {
2874
2875
  image: 'Image',
2875
2876
  file: 'File',
2876
2877
  emoji: 'Emoji',
2878
+ equation: 'Equation',
2877
2879
  removeColor: 'Remove color',
2878
2880
  },
2879
2881
  link: {
@@ -2909,6 +2911,10 @@ var enUS = {
2909
2911
  codeBlock: {
2910
2912
  langType: 'Select language',
2911
2913
  },
2914
+ equation: {
2915
+ save: 'Done',
2916
+ placeholder: 'Type a TeX expression',
2917
+ },
2912
2918
  };
2913
2919
 
2914
2920
  var zhCN = {
@@ -2958,6 +2964,7 @@ var zhCN = {
2958
2964
  image: '图片',
2959
2965
  file: '文件',
2960
2966
  emoji: '表情',
2967
+ equation: '公式',
2961
2968
  removeColor: '默认',
2962
2969
  },
2963
2970
  link: {
@@ -2993,6 +3000,10 @@ var zhCN = {
2993
3000
  codeBlock: {
2994
3001
  langType: '选择代码语言',
2995
3002
  },
3003
+ equation: {
3004
+ save: '确定',
3005
+ placeholder: '请输入 TeX 公式',
3006
+ },
2996
3007
  };
2997
3008
 
2998
3009
  var ja = {
@@ -3042,6 +3053,7 @@ var ja = {
3042
3053
  image: '画像',
3043
3054
  file: 'ファイル',
3044
3055
  emoji: '絵文字',
3056
+ equation: '公式',
3045
3057
  removeColor: 'デフォルト',
3046
3058
  },
3047
3059
  link: {
@@ -3077,6 +3089,10 @@ var ja = {
3077
3089
  codeBlock: {
3078
3090
  langType: 'コード言語を選択',
3079
3091
  },
3092
+ equation: {
3093
+ save: '確認',
3094
+ placeholder: 'TeX 公式を入力してください。',
3095
+ },
3080
3096
  };
3081
3097
 
3082
3098
  var ko = {
@@ -3126,6 +3142,7 @@ var ko = {
3126
3142
  image: '이미지',
3127
3143
  file: '파일',
3128
3144
  emoji: '이모지',
3145
+ equation: '수식',
3129
3146
  removeColor: '기본색',
3130
3147
  },
3131
3148
  link: {
@@ -3161,6 +3178,10 @@ var ko = {
3161
3178
  codeBlock: {
3162
3179
  langType: '코드언어 선택',
3163
3180
  },
3181
+ equation: {
3182
+ save: '확인',
3183
+ placeholder: 'TeX 수식을 입력하세요',
3184
+ },
3164
3185
  };
3165
3186
 
3166
3187
  const localeTranslations = {
@@ -3560,7 +3581,7 @@ class Box {
3560
3581
  if (typeof node === 'string') {
3561
3582
  const component = boxes.get(node);
3562
3583
  if (component === undefined) {
3563
- throw new Error(`Box "${node}" has not been defined yet.`);
3584
+ throw new Error(`The box "${node}" has not been defined yet.`);
3564
3585
  }
3565
3586
  const type = encode(component.type);
3566
3587
  const name = encode(component.name);
@@ -3573,7 +3594,7 @@ class Box {
3573
3594
  this.node = query(node);
3574
3595
  const component = boxes.get(this.name);
3575
3596
  if (component === undefined) {
3576
- throw new Error(`Box "${this.name}" has not been defined yet.`);
3597
+ throw new Error(`The box "${this.name}" has not been defined yet.`);
3577
3598
  }
3578
3599
  if (component.value && !this.node.hasAttr('value')) {
3579
3600
  this.value = component.value;
@@ -3604,7 +3625,7 @@ class Box {
3604
3625
  container.removeClass('lake-box-hovered');
3605
3626
  });
3606
3627
  container.on('click', () => {
3607
- debug(`Box "${this.name}" (id = ${this.node.id}) value:`);
3628
+ debug(`The box "${this.name}" (id = ${this.node.id}) value:`);
3608
3629
  debug(this.value);
3609
3630
  });
3610
3631
  if (this.type === 'block' && this.node.isContentEditable) {
@@ -3643,7 +3664,7 @@ class Box {
3643
3664
  }
3644
3665
  this.value = value;
3645
3666
  }
3646
- // Returns the editor instance of the box.
3667
+ // Returns an instance of the editor that includes the box.
3647
3668
  getEditor() {
3648
3669
  const container = this.node.closest('div[contenteditable]');
3649
3670
  return container.length > 0 ? editors.get(container.id) : undefined;
@@ -3689,15 +3710,16 @@ class Box {
3689
3710
  if (component === undefined) {
3690
3711
  return;
3691
3712
  }
3713
+ this.event.emit('beforeunmount');
3714
+ this.event.removeAllListeners();
3692
3715
  this.addFramework();
3693
3716
  const content = component.render(this);
3694
3717
  if (content !== undefined) {
3695
3718
  const container = this.getContainer();
3696
- const newContainer = container.clone(false);
3697
- newContainer.append(content);
3698
- morph(container, newContainer);
3719
+ container.empty();
3720
+ container.append(content);
3699
3721
  }
3700
- debug(`Box "${this.name}" (id: ${this.node.id}) rendered`);
3722
+ debug(`The box "${this.name}" (id: ${this.node.id}) rendered`);
3701
3723
  }
3702
3724
  // Destroys a rendered box.
3703
3725
  unmount() {
@@ -3705,7 +3727,7 @@ class Box {
3705
3727
  this.event.emit('beforeunmount');
3706
3728
  this.event.removeAllListeners();
3707
3729
  this.node.empty();
3708
- debug(`Box "${this.name}" (id: ${this.node.id}) unmounted`);
3730
+ debug(`The box "${this.name}" (id: ${this.node.id}) unmounted`);
3709
3731
  }
3710
3732
  // Returns a HTML string of the box.
3711
3733
  getHTML() {
@@ -4273,13 +4295,7 @@ class HTMLParser {
4273
4295
  }
4274
4296
  return html.trim();
4275
4297
  }
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.
4298
+ // Returns a document fragment.
4283
4299
  getFragment() {
4284
4300
  const html = this.getHTML();
4285
4301
  const body = this.parseHTML(html);
@@ -5117,7 +5133,7 @@ function removeBox(range) {
5117
5133
  return box;
5118
5134
  }
5119
5135
 
5120
- var version = "0.1.16";
5136
+ var version = "0.1.17";
5121
5137
 
5122
5138
  // Returns the attributes of the element as an key-value object.
5123
5139
  function getAttributes(node) {
@@ -5295,7 +5311,7 @@ class Selection {
5295
5311
  insertBox(boxName, boxValue) {
5296
5312
  const box = insertBox(this.range, boxName, boxValue);
5297
5313
  if (!box) {
5298
- throw new Error(`Box "${boxName}" cannot be inserted outside the editor.`);
5314
+ throw new Error(`The box "${boxName}" cannot be inserted outside the editor.`);
5299
5315
  }
5300
5316
  return box;
5301
5317
  }
@@ -5418,7 +5434,8 @@ class History {
5418
5434
  },
5419
5435
  afterAttributeUpdated: (attributeName, nativeNode) => {
5420
5436
  const node = new Nodes(nativeNode);
5421
- if (attributeName === 'value' && node.name === 'lake-box') {
5437
+ if (['name', 'value'].indexOf(attributeName) >= 0 && node.name === 'lake-box') {
5438
+ getBox(node).unmount();
5422
5439
  const instanceMap = getInstanceMap(container.id);
5423
5440
  instanceMap.delete(node.id);
5424
5441
  }
@@ -5434,7 +5451,7 @@ class History {
5434
5451
  this.removeIdfromBoxes(otherContainer);
5435
5452
  }
5436
5453
  get canUndo() {
5437
- return this.index > 1 && !!this.list[this.index - 1];
5454
+ return this.index > 1 && !!this.list[this.index - 2];
5438
5455
  }
5439
5456
  get canRedo() {
5440
5457
  return !!this.list[this.index];
@@ -5554,53 +5571,65 @@ class History {
5554
5571
  }
5555
5572
  }
5556
5573
 
5557
- const shortenedTypeMap = new Map([
5558
- ['#', 'shift+#'],
5574
+ const aliasMap = new Map([
5575
+ ['arrow-left', 'left'],
5576
+ ['arrow-right', 'right'],
5577
+ ['arrow-up', 'up'],
5578
+ ['arrow-down', 'down'],
5559
5579
  ]);
5560
5580
  class Keystroke {
5561
5581
  constructor(container) {
5562
5582
  this.keydownEventList = [];
5563
5583
  this.keyupEventList = [];
5564
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
+ });
5565
5605
  }
5566
5606
  normalizeType(type) {
5567
5607
  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}`);
5608
+ type = (_a = aliasMap.get(type)) !== null && _a !== void 0 ? _a : type;
5572
5609
  return type;
5573
5610
  }
5574
5611
  // Sets a keydown shortcut.
5575
5612
  setKeydown(type, listener) {
5576
5613
  type = this.normalizeType(type);
5577
- const handler = createKeybindingsHandler({
5578
- [type]: event => listener(event),
5579
- });
5580
5614
  this.keydownEventList.push({
5581
5615
  type,
5582
5616
  listener,
5583
5617
  });
5584
- this.container.on('keydown', handler);
5585
5618
  }
5586
5619
  // Sets a keyup shortcut.
5587
5620
  setKeyup(type, listener) {
5588
5621
  type = this.normalizeType(type);
5589
- const handler = createKeybindingsHandler({
5590
- [type]: event => listener(event),
5591
- });
5592
5622
  this.keyupEventList.push({
5593
5623
  type,
5594
5624
  listener,
5595
5625
  });
5596
- this.container.on('keyup', handler);
5597
5626
  }
5598
5627
  // Executes the keydown shortcuts.
5599
5628
  keydown(type) {
5600
5629
  type = this.normalizeType(type);
5601
5630
  for (const item of this.keydownEventList) {
5602
5631
  if (item.type === type) {
5603
- if (item.listener(new Event(type)) === false) {
5632
+ if (item.listener(new KeyboardEvent(type)) === false) {
5604
5633
  break;
5605
5634
  }
5606
5635
  }
@@ -5611,7 +5640,7 @@ class Keystroke {
5611
5640
  type = this.normalizeType(type);
5612
5641
  for (const item of this.keyupEventList) {
5613
5642
  if (item.type === type) {
5614
- if (item.listener(new Event(type)) === false) {
5643
+ if (item.listener(new KeyboardEvent(type)) === false) {
5615
5644
  break;
5616
5645
  }
5617
5646
  }
@@ -5847,7 +5876,7 @@ class Editor {
5847
5876
  // Adds or Removes a placeholder class.
5848
5877
  togglePlaceholderClass(value) {
5849
5878
  value = denormalizeValue(value);
5850
- const className = 'lake-show-placeholder';
5879
+ const className = 'lake-placeholder';
5851
5880
  if (value.replace('<focus />', '') === '<p><br /></p>') {
5852
5881
  this.container.addClass(className);
5853
5882
  }
@@ -5899,6 +5928,18 @@ class Editor {
5899
5928
  this.container.on('compositionend', () => {
5900
5929
  this.isComposing = false;
5901
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
+ });
5902
5943
  this.container.on('input', event => {
5903
5944
  const inputEvent = event;
5904
5945
  // Here setTimeout is necessary because isComposing is not false after ending composition.
@@ -5910,6 +5951,9 @@ class Editor {
5910
5951
  }
5911
5952
  // isComposing is false after ending composition because compositionend event has been emitted.
5912
5953
  if (this.isComposing) {
5954
+ if (inputEvent.inputType === 'insertCompositionText') {
5955
+ this.container.removeClass('lake-placeholder');
5956
+ }
5913
5957
  this.event.emit('input', inputEvent);
5914
5958
  return;
5915
5959
  }
@@ -5992,6 +6036,7 @@ class Editor {
5992
6036
  }
5993
6037
  // Fixes wrong content, especially empty tag.
5994
6038
  fixContent() {
6039
+ const range = this.selection.range;
5995
6040
  let changed = false;
5996
6041
  let children = this.container.children();
5997
6042
  for (const child of children) {
@@ -6004,7 +6049,7 @@ class Editor {
6004
6049
  children = this.container.children();
6005
6050
  if (children.length === 0) {
6006
6051
  this.container.html('<p><br /></p>');
6007
- this.selection.range.shrinkAfter(this.container);
6052
+ range.shrinkAfter(this.container);
6008
6053
  changed = true;
6009
6054
  debug('Content fixed: default paragraph was added');
6010
6055
  }
@@ -6014,11 +6059,23 @@ class Editor {
6014
6059
  const paragraph = query('<p />');
6015
6060
  child.before(paragraph);
6016
6061
  paragraph.append(child);
6017
- this.selection.range.shrinkAfter(paragraph);
6062
+ range.shrinkAfter(paragraph);
6018
6063
  changed = true;
6019
6064
  debug(`Content fixed: void element "${child.name}" was wrapped in paragraph`);
6020
6065
  }
6021
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
+ }
6022
6079
  return changed;
6023
6080
  }
6024
6081
  // Sets default config for a plugin.
@@ -6660,6 +6717,15 @@ const toolbarItems = [
6660
6717
  editor.command.execute(value);
6661
6718
  },
6662
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
+ },
6663
6729
  {
6664
6730
  name: 'heading',
6665
6731
  type: 'dropdown',
@@ -7029,8 +7095,10 @@ const hrBox = {
7029
7095
  if (!editor) {
7030
7096
  return;
7031
7097
  }
7098
+ const boxContainer = box.getContainer();
7032
7099
  const rootNode = query('<div class="lake-hr"><hr /></div>');
7033
- box.getContainer().append(rootNode);
7100
+ boxContainer.empty();
7101
+ boxContainer.append(rootNode);
7034
7102
  rootNode.on('click', () => {
7035
7103
  editor.selection.selectBox(box);
7036
7104
  });
@@ -7125,10 +7193,10 @@ const codeBlockBox = {
7125
7193
  return;
7126
7194
  }
7127
7195
  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);
7196
+ const boxContainer = box.getContainer();
7197
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7198
+ boxContainer.empty();
7199
+ boxContainer.append(rootNode);
7132
7200
  const codeBlockNativeNode = rootNode.get(0);
7133
7201
  if (!codeBlockNativeNode) {
7134
7202
  return;
@@ -7220,7 +7288,7 @@ const codeBlockBox = {
7220
7288
  });
7221
7289
  dropdown.render();
7222
7290
  const resizeListener = () => {
7223
- container.css('width', `${editor.container.innerWidth() - 2}px`);
7291
+ boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7224
7292
  };
7225
7293
  editor.event.on('resize', resizeListener);
7226
7294
  rootNode.on('click', () => {
@@ -7486,7 +7554,7 @@ function openFullScreen(box) {
7486
7554
  lightbox.loadAndOpen(currentIndex);
7487
7555
  }
7488
7556
  // Displays error icon and filename.
7489
- function renderError(rootNode, box) {
7557
+ function renderError$1(rootNode, box) {
7490
7558
  return __awaiter(this, void 0, void 0, function* () {
7491
7559
  const editor = box.getEditor();
7492
7560
  if (!editor) {
@@ -7531,7 +7599,7 @@ function renderUploading(rootNode, box) {
7531
7599
  const value = box.value;
7532
7600
  const imageInfo = yield getImageInfo(value.url);
7533
7601
  if (!imageInfo.width || !imageInfo.height) {
7534
- yield renderError(rootNode, box);
7602
+ yield renderError$1(rootNode, box);
7535
7603
  return;
7536
7604
  }
7537
7605
  const maxWidth = editor.container.innerWidth() - 2;
@@ -7595,7 +7663,7 @@ function renderDone(rootNode, box) {
7595
7663
  return;
7596
7664
  }
7597
7665
  if (!imageInfo.width || !imageInfo.height) {
7598
- yield renderError(rootNode, box);
7666
+ yield renderError$1(rootNode, box);
7599
7667
  return;
7600
7668
  }
7601
7669
  let width = value.width;
@@ -7668,26 +7736,26 @@ const imageBox = {
7668
7736
  box.node.hide();
7669
7737
  return;
7670
7738
  }
7671
- const container = box.getContainer();
7672
- if (value.width && value.height && container.find('.lake-progress').length === 0) {
7673
- container.css({
7739
+ const boxContainer = box.getContainer();
7740
+ if (value.width && value.height && boxContainer.find('.lake-progress').length === 0) {
7741
+ boxContainer.css({
7674
7742
  width: `${value.width}px`,
7675
7743
  height: `${value.height}px`,
7676
7744
  });
7677
- container.empty();
7745
+ boxContainer.empty();
7678
7746
  const placeholderNode = query('<div class="lake-image-placeholder" />');
7679
- container.append(placeholderNode);
7747
+ boxContainer.append(placeholderNode);
7680
7748
  const imageIcon = icons.get('image');
7681
7749
  if (imageIcon) {
7682
7750
  placeholderNode.append(imageIcon);
7683
7751
  }
7684
7752
  }
7685
- if (container.first().length === 0) {
7753
+ if (boxContainer.first().length === 0) {
7686
7754
  // The following code is for unit testing because some test cases need to
7687
7755
  // select the content of the box before it is completely loaded.
7688
7756
  // Example:
7689
7757
  // range.setStart(box.getContainer(), 1);
7690
- container.append('<div />');
7758
+ boxContainer.append('<div />');
7691
7759
  }
7692
7760
  // for test
7693
7761
  if (value.status === 'loading') {
@@ -7700,14 +7768,14 @@ const imageBox = {
7700
7768
  promise = renderUploading(rootNode, box);
7701
7769
  }
7702
7770
  else if (value.status === 'error') {
7703
- promise = renderError(rootNode, box);
7771
+ promise = renderError$1(rootNode, box);
7704
7772
  }
7705
7773
  else {
7706
7774
  promise = renderDone(rootNode, box);
7707
7775
  }
7708
7776
  promise.then(() => {
7709
- container.empty();
7710
- container.append(rootNode);
7777
+ boxContainer.empty();
7778
+ boxContainer.append(rootNode);
7711
7779
  rootNode.find('.lake-button-view').on('click', () => openFullScreen(box));
7712
7780
  if (editor.readonly) {
7713
7781
  rootNode.find('.lake-button-remove').hide();
@@ -7867,12 +7935,12 @@ const videoBox = {
7867
7935
  showVideo(box);
7868
7936
  },
7869
7937
  });
7870
- formNode.find('input[name="url"]').on('keydown', createKeybindingsHandler({
7871
- 'Enter': event => {
7938
+ formNode.find('input[name="url"]').on('keydown', event => {
7939
+ if (isKeyHotkey('enter', event)) {
7872
7940
  event.preventDefault();
7873
7941
  button.node.emit('click');
7874
- },
7875
- }));
7942
+ }
7943
+ });
7876
7944
  button.render();
7877
7945
  rootNode.append(formNode);
7878
7946
  appendButtonGroup(box);
@@ -7957,12 +8025,12 @@ const fileBox = {
7957
8025
  box.node.hide();
7958
8026
  return;
7959
8027
  }
7960
- const container = box.getContainer();
8028
+ const boxContainer = box.getContainer();
7961
8029
  const rootNode = query('<div class="lake-file" />');
7962
8030
  rootNode.addClass(`lake-file-${value.status}`);
7963
8031
  appendContent(rootNode, box);
7964
- container.empty();
7965
- container.append(rootNode);
8032
+ boxContainer.empty();
8033
+ boxContainer.append(rootNode);
7966
8034
  if (!editor.readonly) {
7967
8035
  rootNode.on('click', () => {
7968
8036
  editor.selection.selectBox(box);
@@ -7987,16 +8055,100 @@ const emojiBox = {
7987
8055
  return;
7988
8056
  }
7989
8057
  const value = box.value;
8058
+ const boxContainer = box.getContainer();
7990
8059
  const rootNode = query(safeTemplate `
7991
8060
  <div class="lake-emoji"><img src="${value.url}" title="${value.title}" /></div>
7992
8061
  `);
7993
- box.getContainer().append(rootNode);
8062
+ boxContainer.empty();
8063
+ boxContainer.append(rootNode);
7994
8064
  rootNode.on('click', () => {
7995
8065
  editor.selection.selectBox(box);
7996
8066
  });
7997
8067
  },
7998
8068
  };
7999
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
+
8000
8152
  var copy = (editor) => {
8001
8153
  editor.event.on('copy', event => {
8002
8154
  const range = editor.selection.range;
@@ -8431,6 +8583,7 @@ var undo = (editor) => {
8431
8583
  return;
8432
8584
  }
8433
8585
  editor.command.add('undo', {
8586
+ isDisabled: () => !editor.history.canUndo,
8434
8587
  execute: () => {
8435
8588
  editor.history.undo();
8436
8589
  },
@@ -8450,6 +8603,7 @@ var redo = (editor) => {
8450
8603
  return;
8451
8604
  }
8452
8605
  editor.command.add('redo', {
8606
+ isDisabled: () => !editor.history.canRedo,
8453
8607
  execute: () => {
8454
8608
  editor.history.redo();
8455
8609
  },
@@ -9090,18 +9244,18 @@ class LinkPopup {
9090
9244
  },
9091
9245
  });
9092
9246
  button.render();
9093
- this.container.find('input[name="url"]').on('keydown', createKeybindingsHandler({
9094
- 'Enter': event => {
9247
+ this.container.find('input[name="url"]').on('keydown', event => {
9248
+ if (isKeyHotkey('enter', event)) {
9095
9249
  event.preventDefault();
9096
9250
  button.node.emit('click');
9097
- },
9098
- }));
9099
- this.container.find('input[name="title"]').on('keydown', createKeybindingsHandler({
9100
- 'Enter': event => {
9251
+ }
9252
+ });
9253
+ this.container.find('input[name="title"]').on('keydown', event => {
9254
+ if (isKeyHotkey('enter', event)) {
9101
9255
  event.preventDefault();
9102
9256
  button.node.emit('click');
9103
- },
9104
- }));
9257
+ }
9258
+ });
9105
9259
  }
9106
9260
  // Remove link
9107
9261
  appendUnlinkButton() {
@@ -9424,6 +9578,36 @@ var emoji = (editor) => {
9424
9578
  });
9425
9579
  };
9426
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
+
9427
9611
  const headingTypeMap = new Map([
9428
9612
  ['#', 'h1'],
9429
9613
  ['##', 'h2'],
@@ -10046,7 +10230,8 @@ var backspaceKey = (editor) => {
10046
10230
  editor.history.save();
10047
10231
  return;
10048
10232
  }
10049
- if (prevNode.name === 'br' && prevNode.prev().length > 0) {
10233
+ const nextNode = range.getNextNode();
10234
+ if (prevNode.name === 'br' && nextNode.length > 0) {
10050
10235
  event.preventDefault();
10051
10236
  range.setStartBefore(prevNode);
10052
10237
  range.collapseToStart();
@@ -10194,8 +10379,12 @@ var tabKey = (editor) => {
10194
10379
  if (editor.config.indentWithTab === false) {
10195
10380
  return;
10196
10381
  }
10382
+ const range = editor.selection.range;
10383
+ if (range.isInsideBox) {
10384
+ return;
10385
+ }
10197
10386
  event.preventDefault();
10198
- const blocks = editor.selection.range.getBlocks();
10387
+ const blocks = range.getBlocks();
10199
10388
  for (const block of blocks) {
10200
10389
  if (block.name !== 'p' || block.css('text-indent') === '2em') {
10201
10390
  setBlockIndent(block, 'increase');
@@ -10359,6 +10548,7 @@ Editor.box.add(imageBox);
10359
10548
  Editor.box.add(videoBox);
10360
10549
  Editor.box.add(fileBox);
10361
10550
  Editor.box.add(emojiBox);
10551
+ Editor.box.add(equationBox);
10362
10552
  Editor.plugin.add(copy);
10363
10553
  Editor.plugin.add(cut);
10364
10554
  Editor.plugin.add(paste);
@@ -10391,6 +10581,8 @@ Editor.plugin.add(image);
10391
10581
  Editor.plugin.add(video);
10392
10582
  Editor.plugin.add(file);
10393
10583
  Editor.plugin.add(emoji);
10584
+ Editor.plugin.add(equation);
10585
+ Editor.plugin.add(specialCharacter);
10394
10586
  Editor.plugin.add(markdown);
10395
10587
  Editor.plugin.add(enterKey);
10396
10588
  Editor.plugin.add(shiftEnterKey);