lakelib 0.2.1 → 0.2.2

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
@@ -813,8 +813,8 @@ class Nodes {
813
813
  // Traverses the first node and its parents until it finds an element which can scroll.
814
814
  closestScroller() {
815
815
  let parent = this.eq(0);
816
- while (parent.length > 0 && parent.isElement) {
817
- if (['scroll', 'auto'].indexOf(parent.computedCSS('overflow-y')) >= 0) {
816
+ while (parent.length > 0) {
817
+ if (parent.isElement && ['scroll', 'auto'].indexOf(parent.computedCSS('overflow-y')) >= 0) {
818
818
  return parent;
819
819
  }
820
820
  parent = parent.parent();
@@ -1828,7 +1828,7 @@ class Range {
1828
1828
  return marks;
1829
1829
  }
1830
1830
  // Returns the text of the start part of the closest block divided into two parts by the start point of the range.
1831
- // "<p>one<anchor />two<focus />three</p>" returns "three".
1831
+ // "<p>one<anchor />two<focus />three</p>" returns "one".
1832
1832
  getStartText() {
1833
1833
  const node = this.startNode;
1834
1834
  const offset = this.startOffset;
@@ -1873,6 +1873,45 @@ class Range {
1873
1873
  }
1874
1874
  return text;
1875
1875
  }
1876
+ // Returns a new text range from the specified character to the beginning of the current range.
1877
+ // The specified character must be preceded by a whitespace or be at the beginning of a paragraph,
1878
+ // without being adjacent to other characters. If these conditions are not met, it returns null.
1879
+ getCharacterRange(character) {
1880
+ const newRange = this.clone();
1881
+ newRange.shrink();
1882
+ if (newRange.startOffset === 0) {
1883
+ return null;
1884
+ }
1885
+ if (newRange.startNode.isElement) {
1886
+ const textNode = newRange.startNode.children()[newRange.startOffset - 1];
1887
+ if (!textNode.isText) {
1888
+ return null;
1889
+ }
1890
+ newRange.setEnd(textNode, textNode.text().length);
1891
+ newRange.collapseToEnd();
1892
+ }
1893
+ const textNode = newRange.startNode;
1894
+ const text = textNode.text().slice(0, newRange.startOffset);
1895
+ const lastIndexOfNormalSpace = text.lastIndexOf(` ${character}`);
1896
+ const lastIndexOfNoBreakSpace = text.lastIndexOf(`\xA0${character}`);
1897
+ const lastIndexOfZeroWidthSpace = text.lastIndexOf(`\u200B${character}`);
1898
+ if (lastIndexOfNormalSpace >= 0) {
1899
+ newRange.setStart(textNode, lastIndexOfNormalSpace + 1);
1900
+ }
1901
+ else if (lastIndexOfNoBreakSpace >= 0) {
1902
+ newRange.setStart(textNode, lastIndexOfNoBreakSpace + 1);
1903
+ }
1904
+ else if (lastIndexOfZeroWidthSpace >= 0) {
1905
+ newRange.setStart(textNode, lastIndexOfZeroWidthSpace + 1);
1906
+ }
1907
+ else if (text.indexOf(character) === 0) {
1908
+ newRange.setStart(textNode, 0);
1909
+ }
1910
+ else {
1911
+ return null;
1912
+ }
1913
+ return newRange;
1914
+ }
1876
1915
  // Returns a range object with boundary points identical to the cloned range.
1877
1916
  clone() {
1878
1917
  return new Range(this.range.cloneRange());
@@ -3422,7 +3461,7 @@ loadAllLocales();
3422
3461
 
3423
3462
  class Dropdown {
3424
3463
  constructor(config) {
3425
- this.documentClickListener = (event) => {
3464
+ this.clickListener = (event) => {
3426
3465
  const targetNode = new Nodes(event.target);
3427
3466
  const titleNode = this.node.find('.lake-dropdown-title');
3428
3467
  if (targetNode.closest('.lake-dropdown-title').get(0) === titleNode.get(0)) {
@@ -3430,18 +3469,22 @@ class Dropdown {
3430
3469
  }
3431
3470
  this.hideMenu();
3432
3471
  };
3472
+ this.scrollListener = () => this.updatePosition();
3473
+ this.resizeListener = () => this.updatePosition();
3433
3474
  this.config = config;
3434
3475
  this.root = config.root;
3435
3476
  this.locale = config.locale || i18nObject('en-US');
3436
- const placement = config.placement || 'bottom';
3477
+ this.location = config.location || 'local';
3478
+ this.direction = config.direction || 'auto';
3437
3479
  this.node = query(safeTemplate `
3438
- <div class="lake-dropdown lake-${config.menuType}-dropdown" name="${config.name}" placement="${placement}">
3480
+ <div class="lake-dropdown lake-${config.menuType}-dropdown" name="${config.name}">
3439
3481
  <button type="button" name="${config.name}" class="lake-dropdown-title">
3440
3482
  <div class="lake-dropdown-${config.icon ? 'icon' : 'text'}"></div>
3441
3483
  <div class="lake-dropdown-down-icon"></div>
3442
3484
  </button>
3443
3485
  </div>
3444
3486
  `);
3487
+ this.menuNode = query('<ul class="lake-popup lake-dropdown-menu lake-custom-properties" />');
3445
3488
  if (config.tabIndex !== undefined) {
3446
3489
  const titleNode = this.node.find('.lake-dropdown-title');
3447
3490
  titleNode.attr('tabindex', config.tabIndex.toString());
@@ -3520,14 +3563,97 @@ class Dropdown {
3520
3563
  }
3521
3564
  }
3522
3565
  }
3566
+ appendMenu() {
3567
+ const config = this.config;
3568
+ const menuNode = this.menuNode;
3569
+ const titleNode = this.node.find('.lake-dropdown-title');
3570
+ const textNode = titleNode.find('.lake-dropdown-text');
3571
+ menuNode.addClass(`lake-${config.menuType}-dropdown-menu`);
3572
+ if (config.menuWidth) {
3573
+ menuNode.css('width', config.menuWidth);
3574
+ }
3575
+ if (config.menuHeight) {
3576
+ menuNode.addClass('lake-dropdown-menu-with-scroll');
3577
+ menuNode.css('height', config.menuHeight);
3578
+ }
3579
+ this.apppendMenuItems(menuNode);
3580
+ if (this.location === 'local') {
3581
+ this.node.append(menuNode);
3582
+ }
3583
+ else {
3584
+ query(document.body).append(menuNode);
3585
+ }
3586
+ menuNode.on('click', event => {
3587
+ event.preventDefault();
3588
+ event.stopPropagation();
3589
+ const listItem = query(event.target).closest('li');
3590
+ if (listItem.length === 0) {
3591
+ return;
3592
+ }
3593
+ const value = listItem.attr('value');
3594
+ Dropdown.setValue(this.node, [value]);
3595
+ if (textNode.length > 0) {
3596
+ textNode.text(listItem.text());
3597
+ }
3598
+ if (config.menuType === 'color' && value !== '') {
3599
+ this.node.attr('color', value);
3600
+ this.updateColorAccent(titleNode, value);
3601
+ }
3602
+ config.onSelect(value);
3603
+ this.hideMenu();
3604
+ });
3605
+ }
3606
+ updatePosition() {
3607
+ const menuNode = this.menuNode;
3608
+ const dropdownNativeNode = this.node.get(0);
3609
+ const dropdownRect = dropdownNativeNode.getBoundingClientRect();
3610
+ // A overflow width on the left side, greater than 0 indicates an overflow.
3611
+ const leftOverflow = menuNode.width() - (dropdownRect.x + dropdownRect.width);
3612
+ // A overflow width on the right side, greater than 0 indicates an overflow.
3613
+ const rightOverflow = dropdownRect.x + menuNode.width() - window.innerWidth;
3614
+ const rightToLeft = rightOverflow + 50 > 0 && (leftOverflow < 0 || leftOverflow < rightOverflow);
3615
+ if (this.location === 'local') {
3616
+ if (rightToLeft) {
3617
+ menuNode.css('left', 'auto');
3618
+ menuNode.css('right', '0');
3619
+ }
3620
+ else {
3621
+ menuNode.css('left', '');
3622
+ menuNode.css('right', '');
3623
+ }
3624
+ if (this.direction === 'top') {
3625
+ menuNode.css('top', 'auto');
3626
+ menuNode.css('bottom', `${dropdownRect.height}px`);
3627
+ }
3628
+ return;
3629
+ }
3630
+ // A overflow width on the bottom side, greater than 0 indicates an overflow.
3631
+ const bottomOverflow = dropdownRect.y + dropdownRect.height + menuNode.height() - window.innerHeight;
3632
+ const dropdownX = dropdownRect.x + window.scrollX;
3633
+ const dropdownY = dropdownRect.y + window.scrollY;
3634
+ if (rightToLeft) {
3635
+ menuNode.css('left', `${dropdownX - menuNode.width() + dropdownRect.width}px`);
3636
+ }
3637
+ else {
3638
+ menuNode.css('left', `${dropdownX}px`);
3639
+ }
3640
+ if (bottomOverflow > 0) {
3641
+ menuNode.css('top', `${dropdownY - menuNode.height()}px`);
3642
+ }
3643
+ else {
3644
+ menuNode.css('top', `${dropdownY + dropdownRect.height}px`);
3645
+ }
3646
+ }
3523
3647
  showMenu() {
3524
3648
  const config = this.config;
3525
- const dropdownNode = this.node;
3526
- const menuNode = dropdownNode.find('.lake-dropdown-menu');
3527
- if (dropdownNode.attr('disabled')) {
3649
+ const menuNode = this.menuNode;
3650
+ if (!menuNode.get(0).isConnected) {
3651
+ this.appendMenu();
3652
+ }
3653
+ if (this.node.attr('disabled')) {
3528
3654
  return;
3529
3655
  }
3530
- const currentValues = Dropdown.getValue(dropdownNode);
3656
+ const currentValues = Dropdown.getValue(this.node);
3531
3657
  menuNode.find('.lake-dropdown-menu-check').css('visibility', 'hidden');
3532
3658
  menuNode.find('li').each(node => {
3533
3659
  const listNode = query(node);
@@ -3537,40 +3663,62 @@ class Dropdown {
3537
3663
  });
3538
3664
  menuNode.css('visibility', 'hidden');
3539
3665
  menuNode.show(config.menuType === 'list' ? 'block' : 'flex');
3540
- const dropdownNativeNode = dropdownNode.get(0);
3541
- const dropdownRect = dropdownNativeNode.getBoundingClientRect();
3542
- // A overflow width on the left side, greater than 0 indicates an overflow.
3543
- const leftOverflow = menuNode.width() - (dropdownRect.x + dropdownRect.width);
3544
- // A overflow width on the right side, greater than 0 indicates an overflow.
3545
- const rightOverflow = dropdownRect.x + menuNode.width() - window.innerWidth;
3546
- if (rightOverflow + 50 > 0 && (leftOverflow < 0 || leftOverflow < rightOverflow)) {
3547
- menuNode.css('left', 'auto');
3548
- menuNode.css('right', '0');
3549
- }
3550
- else {
3551
- menuNode.css('left', '');
3552
- menuNode.css('right', '');
3666
+ this.updatePosition();
3667
+ const viewport = this.node.closestScroller();
3668
+ if (viewport.length > 0) {
3669
+ viewport.on('scroll', this.scrollListener);
3553
3670
  }
3671
+ document.addEventListener('click', this.clickListener);
3672
+ window.addEventListener('resize', this.resizeListener);
3554
3673
  menuNode.css('visibility', '');
3555
- document.addEventListener('click', this.documentClickListener);
3556
3674
  }
3557
3675
  hideMenu() {
3558
- const dropdownNode = this.node;
3559
- const menuNode = dropdownNode.find('.lake-dropdown-menu');
3560
- menuNode.hide();
3561
- document.removeEventListener('click', this.documentClickListener);
3676
+ this.menuNode.hide();
3677
+ const viewport = this.node.closestScroller();
3678
+ if (viewport.length > 0) {
3679
+ viewport.off('scroll', this.scrollListener);
3680
+ }
3681
+ document.removeEventListener('click', this.clickListener);
3682
+ window.removeEventListener('resize', this.resizeListener);
3562
3683
  }
3563
- bindEvents() {
3684
+ render() {
3685
+ var _a, _b;
3564
3686
  const config = this.config;
3565
- const dropdownNode = this.node;
3566
- const titleNode = dropdownNode.find('.lake-dropdown-title');
3687
+ const defaultValue = (_a = config.defaultValue) !== null && _a !== void 0 ? _a : '';
3688
+ const titleNode = this.node.find('.lake-dropdown-title');
3689
+ if (!config.downIcon) {
3690
+ titleNode.addClass('lake-dropdown-title-no-down');
3691
+ }
3692
+ if (config.width) {
3693
+ titleNode.css('width', config.width);
3694
+ }
3695
+ const tooltip = typeof config.tooltip === 'string' ? config.tooltip : config.tooltip(this.locale);
3696
+ titleNode.attr('title', tooltip);
3567
3697
  const textNode = titleNode.find('.lake-dropdown-text');
3568
3698
  const iconNode = titleNode.find('.lake-dropdown-icon');
3699
+ if (config.icon) {
3700
+ iconNode.append(config.icon);
3701
+ }
3702
+ if (config.accentIcon) {
3703
+ iconNode.append(config.accentIcon);
3704
+ }
3569
3705
  const downIconNode = titleNode.find('.lake-dropdown-down-icon');
3570
- const menuNode = dropdownNode.find('.lake-dropdown-menu');
3706
+ if (config.downIcon) {
3707
+ downIconNode.append(config.downIcon);
3708
+ }
3709
+ Dropdown.setValue(this.node, [defaultValue]);
3710
+ if (textNode.length > 0) {
3711
+ const menuMap = Dropdown.getMenuMap(config.menuItems, this.locale);
3712
+ textNode.text((_b = menuMap.get(defaultValue)) !== null && _b !== void 0 ? _b : defaultValue);
3713
+ }
3714
+ if (config.menuType === 'color') {
3715
+ this.updateColorAccent(titleNode, defaultValue);
3716
+ }
3717
+ this.node.append(titleNode);
3718
+ this.root.append(this.node);
3571
3719
  if (config.menuType === 'color') {
3572
3720
  iconNode.on('mouseenter', () => {
3573
- if (dropdownNode.attr('disabled')) {
3721
+ if (this.node.attr('disabled')) {
3574
3722
  return;
3575
3723
  }
3576
3724
  iconNode.addClass('lake-dropdown-icon-hovered');
@@ -3579,7 +3727,7 @@ class Dropdown {
3579
3727
  iconNode.removeClass('lake-dropdown-icon-hovered');
3580
3728
  });
3581
3729
  downIconNode.on('mouseenter', () => {
3582
- if (dropdownNode.attr('disabled')) {
3730
+ if (this.node.attr('disabled')) {
3583
3731
  return;
3584
3732
  }
3585
3733
  downIconNode.addClass('lake-dropdown-down-icon-hovered');
@@ -3590,7 +3738,7 @@ class Dropdown {
3590
3738
  }
3591
3739
  else {
3592
3740
  titleNode.on('mouseenter', () => {
3593
- if (dropdownNode.attr('disabled')) {
3741
+ if (this.node.attr('disabled')) {
3594
3742
  return;
3595
3743
  }
3596
3744
  titleNode.addClass('lake-dropdown-title-hovered');
@@ -3602,10 +3750,10 @@ class Dropdown {
3602
3750
  if (config.menuType === 'color') {
3603
3751
  iconNode.on('click', event => {
3604
3752
  event.preventDefault();
3605
- if (dropdownNode.attr('disabled')) {
3753
+ if (this.node.attr('disabled')) {
3606
3754
  return;
3607
3755
  }
3608
- const value = dropdownNode.attr('color') || config.defaultValue || '';
3756
+ const value = this.node.attr('color') || config.defaultValue || '';
3609
3757
  config.onSelect(value);
3610
3758
  });
3611
3759
  }
@@ -3614,74 +3762,6 @@ class Dropdown {
3614
3762
  event.preventDefault();
3615
3763
  this.showMenu();
3616
3764
  });
3617
- menuNode.on('click', event => {
3618
- event.preventDefault();
3619
- event.stopPropagation();
3620
- const listItem = query(event.target).closest('li');
3621
- if (listItem.length === 0) {
3622
- return;
3623
- }
3624
- const value = listItem.attr('value');
3625
- Dropdown.setValue(dropdownNode, [value]);
3626
- if (textNode.length > 0) {
3627
- textNode.text(listItem.text());
3628
- }
3629
- if (config.menuType === 'color' && value !== '') {
3630
- dropdownNode.attr('color', value);
3631
- this.updateColorAccent(titleNode, value);
3632
- }
3633
- config.onSelect(value);
3634
- this.hideMenu();
3635
- });
3636
- }
3637
- render() {
3638
- var _a, _b;
3639
- const config = this.config;
3640
- const defaultValue = (_a = config.defaultValue) !== null && _a !== void 0 ? _a : '';
3641
- const dropdownNode = this.node;
3642
- const titleNode = dropdownNode.find('.lake-dropdown-title');
3643
- if (!config.downIcon) {
3644
- titleNode.addClass('lake-dropdown-title-no-down');
3645
- }
3646
- if (config.width) {
3647
- titleNode.css('width', config.width);
3648
- }
3649
- const tooltip = typeof config.tooltip === 'string' ? config.tooltip : config.tooltip(this.locale);
3650
- titleNode.attr('title', tooltip);
3651
- const textNode = titleNode.find('.lake-dropdown-text');
3652
- const iconNode = titleNode.find('.lake-dropdown-icon');
3653
- if (config.icon) {
3654
- iconNode.append(config.icon);
3655
- }
3656
- if (config.accentIcon) {
3657
- iconNode.append(config.accentIcon);
3658
- }
3659
- const downIconNode = titleNode.find('.lake-dropdown-down-icon');
3660
- if (config.downIcon) {
3661
- downIconNode.append(config.downIcon);
3662
- }
3663
- const menuNode = query('<ul class="lake-dropdown-menu" />');
3664
- menuNode.addClass(`lake-${config.menuType}-dropdown-menu`);
3665
- if (config.menuWidth) {
3666
- menuNode.css('width', config.menuWidth);
3667
- }
3668
- if (config.menuHeight) {
3669
- menuNode.addClass('lake-dropdown-menu-with-scroll');
3670
- menuNode.css('height', config.menuHeight);
3671
- }
3672
- Dropdown.setValue(dropdownNode, [defaultValue]);
3673
- if (textNode.length > 0) {
3674
- const menuMap = Dropdown.getMenuMap(config.menuItems, this.locale);
3675
- textNode.text((_b = menuMap.get(defaultValue)) !== null && _b !== void 0 ? _b : defaultValue);
3676
- }
3677
- if (config.menuType === 'color') {
3678
- this.updateColorAccent(titleNode, defaultValue);
3679
- }
3680
- this.apppendMenuItems(menuNode);
3681
- dropdownNode.append(titleNode);
3682
- dropdownNode.append(menuNode);
3683
- this.root.append(dropdownNode);
3684
- this.bindEvents();
3685
3765
  }
3686
3766
  unmount() {
3687
3767
  this.hideMenu();
@@ -3694,12 +3774,13 @@ class BoxToolbar {
3694
3774
  this.buttonItemList = [];
3695
3775
  this.dropdownItemList = [];
3696
3776
  this.dropdownList = [];
3697
- this.root = query(config.root);
3777
+ this.scrollListener = () => this.updatePosition();
3778
+ this.resizeListener = () => this.updatePosition();
3698
3779
  this.box = config.box;
3699
3780
  this.items = config.items;
3700
3781
  this.locale = config.locale || i18nObject('en-US');
3701
3782
  this.placement = config.placement || 'top';
3702
- this.container = query('<div class="lake-box-toolbar" />');
3783
+ this.container = query('<div class="lake-popup lake-box-toolbar lake-custom-properties" />');
3703
3784
  }
3704
3785
  appendDivider() {
3705
3786
  this.container.append('<div class="lake-toolbar-divider" />');
@@ -3731,7 +3812,7 @@ class BoxToolbar {
3731
3812
  menuType: item.menuType,
3732
3813
  menuItems: item.menuItems,
3733
3814
  tabIndex: -1,
3734
- placement: this.placement === 'top' ? 'bottom' : 'top',
3815
+ direction: this.placement === 'top' ? 'bottom' : 'top',
3735
3816
  onSelect: value => {
3736
3817
  item.onSelect(this.box, value);
3737
3818
  },
@@ -3739,7 +3820,7 @@ class BoxToolbar {
3739
3820
  dropdown.render();
3740
3821
  this.dropdownList.push(dropdown);
3741
3822
  }
3742
- position() {
3823
+ updatePosition() {
3743
3824
  const boxNode = this.box.node;
3744
3825
  const boxNativeNode = boxNode.get(0);
3745
3826
  const boxRect = boxNativeNode.getBoundingClientRect();
@@ -3760,7 +3841,7 @@ class BoxToolbar {
3760
3841
  }
3761
3842
  // Renders a toolbar for the specified box.
3762
3843
  render() {
3763
- this.root.append(this.container);
3844
+ query(document.body).append(this.container);
3764
3845
  for (const item of this.items) {
3765
3846
  if (item === '|') {
3766
3847
  this.appendDivider();
@@ -3774,13 +3855,23 @@ class BoxToolbar {
3774
3855
  this.appendDropdown(item);
3775
3856
  }
3776
3857
  }
3777
- this.position();
3858
+ this.updatePosition();
3859
+ const viewport = this.box.node.closestScroller();
3860
+ if (viewport.length > 0) {
3861
+ viewport.on('scroll', this.scrollListener);
3862
+ }
3863
+ window.addEventListener('resize', this.resizeListener);
3778
3864
  }
3779
3865
  // Destroys the toolbar.
3780
3866
  unmount() {
3781
3867
  for (const dropdown of this.dropdownList) {
3782
3868
  dropdown.unmount();
3783
3869
  }
3870
+ const viewport = this.box.node.closestScroller();
3871
+ if (viewport.length > 0) {
3872
+ viewport.off('scroll', this.scrollListener);
3873
+ }
3874
+ window.removeEventListener('resize', this.resizeListener);
3784
3875
  this.container.remove();
3785
3876
  }
3786
3877
  }
@@ -3900,31 +3991,19 @@ class Box {
3900
3991
  }
3901
3992
  catch ( /* empty */_a) { /* empty */ }
3902
3993
  let toolbar = null;
3903
- const scrollListener = () => {
3904
- if (toolbar) {
3905
- toolbar.position();
3906
- }
3907
- };
3908
3994
  this.event.on('focus', () => {
3909
3995
  toolbar = new BoxToolbar({
3910
- root: editor ? editor.popupContainer : query(document.body),
3911
3996
  box: this,
3912
3997
  items,
3913
3998
  locale: editor ? editor.locale : undefined,
3914
3999
  });
3915
4000
  toolbar.render();
3916
- if (editor) {
3917
- editor.root.on('scroll', scrollListener);
3918
- }
3919
4001
  });
3920
4002
  this.event.on('blur', () => {
3921
4003
  if (toolbar) {
3922
4004
  toolbar.unmount();
3923
4005
  toolbar = null;
3924
4006
  }
3925
- if (editor) {
3926
- editor.root.off('scroll', scrollListener);
3927
- }
3928
4007
  });
3929
4008
  }
3930
4009
  // Renders the contents of the box.
@@ -4075,12 +4154,14 @@ function request(option) {
4075
4154
  }
4076
4155
  formData.append(key, value);
4077
4156
  });
4078
- const filename = option.filename || 'file';
4079
- if (option.file instanceof Blob) {
4080
- formData.append(filename, option.file, option.file.name);
4081
- }
4082
- else {
4083
- formData.append(filename, option.file);
4157
+ if (option.file) {
4158
+ const filename = option.filename || 'file';
4159
+ if (option.file instanceof Blob) {
4160
+ formData.append(filename, option.file, option.file.name);
4161
+ }
4162
+ else {
4163
+ formData.append(filename, option.file);
4164
+ }
4084
4165
  }
4085
4166
  xhr.onerror = (e) => {
4086
4167
  if (option.onError) {
@@ -5294,6 +5375,7 @@ function insertBox(range, boxName, boxValue) {
5294
5375
  fragment.appendChild(box.node.get(0));
5295
5376
  // inline box
5296
5377
  if (box.type === 'inline') {
5378
+ splitMarks(range);
5297
5379
  insertFragment(range, fragment);
5298
5380
  box.render();
5299
5381
  range.selectBoxEnd(box.node);
@@ -5359,7 +5441,7 @@ function removeBox(range) {
5359
5441
  return box;
5360
5442
  }
5361
5443
 
5362
- var version = "0.2.1";
5444
+ var version = "0.2.2";
5363
5445
 
5364
5446
  // Returns the attributes of the element as an key-value object.
5365
5447
  function getAttributes(node) {
@@ -5947,6 +6029,7 @@ const defaultConfig = {
5947
6029
  }
5948
6030
  },
5949
6031
  slash: false,
6032
+ mention: false,
5950
6033
  };
5951
6034
  class Editor {
5952
6035
  constructor(config) {
@@ -5962,6 +6045,7 @@ class Editor {
5962
6045
  this.isComposing = false;
5963
6046
  this.event = new EventEmitter();
5964
6047
  this.box = Editor.box;
6048
+ this.popup = null;
5965
6049
  this.copyListener = event => {
5966
6050
  const range = this.selection.getCurrentRange();
5967
6051
  if (!this.container.contains(range.commonAncestor)) {
@@ -5995,12 +6079,6 @@ class Editor {
5995
6079
  }
5996
6080
  this.event.emit('click', targetNode);
5997
6081
  };
5998
- this.resizeListener = () => {
5999
- this.event.emit('resize');
6000
- };
6001
- this.scrollListener = () => {
6002
- this.event.emit('scroll');
6003
- };
6004
6082
  this.updateSelectionRange = debounce(() => {
6005
6083
  this.selection.updateByRange();
6006
6084
  }, 1, {
@@ -6110,13 +6188,13 @@ class Editor {
6110
6188
  this.containerWrapper = query('<div class="lake-container-wrapper" />');
6111
6189
  this.container = query('<div class="lake-container" />');
6112
6190
  this.overlayContainer = query('<div class="lake-overlay" />');
6113
- this.popupContainer = query('<div class="lake-popup lake-custom-properties" />');
6114
6191
  this.readonly = this.config.readonly;
6115
6192
  this.root.addClass('lake-custom-properties');
6116
6193
  this.container.attr({
6117
6194
  contenteditable: this.readonly ? 'false' : 'true',
6118
6195
  spellcheck: this.config.spellcheck ? 'true' : 'false',
6119
6196
  tabindex: this.config.tabIndex.toString(),
6197
+ 'data-readonly': this.readonly ? 'true' : 'false',
6120
6198
  });
6121
6199
  if (this.config.placeholder !== '') {
6122
6200
  this.container.attr('placeholder', this.config.placeholder);
@@ -6419,7 +6497,6 @@ class Editor {
6419
6497
  this.root.append(this.containerWrapper);
6420
6498
  this.containerWrapper.append(this.container);
6421
6499
  this.containerWrapper.append(this.overlayContainer);
6422
- query(document.body).append(this.popupContainer);
6423
6500
  this.togglePlaceholderClass(htmlParser.getHTML());
6424
6501
  this.container.append(fragment);
6425
6502
  this.unmountPluginMap = Editor.plugin.loadAll(this);
@@ -6433,9 +6510,7 @@ class Editor {
6433
6510
  if (this.toolbar) {
6434
6511
  this.toolbar.render(this);
6435
6512
  }
6436
- this.root.on('scroll', this.scrollListener);
6437
6513
  document.addEventListener('copy', this.copyListener);
6438
- window.addEventListener('resize', this.resizeListener);
6439
6514
  if (!this.readonly) {
6440
6515
  document.addEventListener('cut', this.cutListener);
6441
6516
  document.addEventListener('paste', this.pasteListener);
@@ -6472,9 +6547,7 @@ class Editor {
6472
6547
  this.root.off();
6473
6548
  this.root.removeClass('lake-custom-properties');
6474
6549
  this.root.empty();
6475
- this.popupContainer.remove();
6476
6550
  document.removeEventListener('copy', this.copyListener);
6477
- window.removeEventListener('resize', this.resizeListener);
6478
6551
  if (!this.readonly) {
6479
6552
  document.removeEventListener('cut', this.cutListener);
6480
6553
  document.removeEventListener('paste', this.pasteListener);
@@ -7199,7 +7272,8 @@ class Toolbar {
7199
7272
  menuWidth: item.menuWidth,
7200
7273
  menuHeight: item.menuHeight,
7201
7274
  tabIndex: -1,
7202
- placement: this.placement === 'top' ? 'bottom' : 'top',
7275
+ location: 'local',
7276
+ direction: this.placement === 'top' ? 'bottom' : 'top',
7203
7277
  onSelect: value => {
7204
7278
  editor.focus();
7205
7279
  item.onSelect(editor, value);
@@ -7458,7 +7532,6 @@ var codeBlockBox = {
7458
7532
  const editor = box.getEditor();
7459
7533
  const rootNode = query('<div class="lake-code-block" />');
7460
7534
  const boxContainer = box.getContainer();
7461
- boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7462
7535
  boxContainer.empty();
7463
7536
  boxContainer.append(rootNode);
7464
7537
  const codeBlockNativeNode = rootNode.get(0);
@@ -7534,6 +7607,7 @@ var codeBlockBox = {
7534
7607
  downIcon: icons.get('down'),
7535
7608
  defaultValue: langItem ? boxValue.lang : codeBlockConfig.defaultLang,
7536
7609
  tooltip: editor.locale.codeBlock.langType(),
7610
+ location: 'global',
7537
7611
  menuType: 'list',
7538
7612
  menuHeight: '200px',
7539
7613
  menuItems: langItems.map((item) => ({
@@ -7551,10 +7625,6 @@ var codeBlockBox = {
7551
7625
  },
7552
7626
  });
7553
7627
  dropdown.render();
7554
- const resizeListener = () => {
7555
- boxContainer.css('width', `${editor.container.innerWidth() - 2}px`);
7556
- };
7557
- editor.event.on('resize', resizeListener);
7558
7628
  rootNode.on('click', () => {
7559
7629
  if (codeEditor.hasFocus) {
7560
7630
  return;
@@ -7564,7 +7634,6 @@ var codeBlockBox = {
7564
7634
  box.event.on('beforeunmount', () => {
7565
7635
  dropdown.unmount();
7566
7636
  codeEditor.destroy();
7567
- editor.event.off('resize', resizeListener);
7568
7637
  debug('CodeMirror destroyed');
7569
7638
  });
7570
7639
  },
@@ -7847,8 +7916,12 @@ function renderError$1(rootNode, box) {
7847
7916
  function renderUploading(rootNode, box) {
7848
7917
  return __awaiter(this, void 0, void 0, function* () {
7849
7918
  const editor = box.getEditor();
7919
+ const boxContainer = box.getContainer();
7850
7920
  const value = box.value;
7851
7921
  const imageInfo = yield getImageInfo(value.url);
7922
+ if (!boxContainer.get(0).isConnected) {
7923
+ return;
7924
+ }
7852
7925
  if (!imageInfo.width || !imageInfo.height) {
7853
7926
  yield renderError$1(rootNode, box);
7854
7927
  return;
@@ -8371,6 +8444,32 @@ var equationBox = {
8371
8444
  },
8372
8445
  };
8373
8446
 
8447
+ var mentionBox = {
8448
+ type: 'inline',
8449
+ name: 'mention',
8450
+ render: box => {
8451
+ var _a;
8452
+ const editor = box.getEditor();
8453
+ const { getProfileUrl } = editor.config.mention;
8454
+ const value = box.value;
8455
+ const url = getProfileUrl ? getProfileUrl(value) : '#';
8456
+ const boxContainer = box.getContainer();
8457
+ const rootNode = query(safeTemplate `
8458
+ <div class="lake-mention"><a href="${url}">@${(_a = value.nickname) !== null && _a !== void 0 ? _a : value.name}</a></div>
8459
+ `);
8460
+ boxContainer.empty();
8461
+ boxContainer.append(rootNode);
8462
+ rootNode.on('click', () => {
8463
+ editor.selection.selectBox(box);
8464
+ });
8465
+ if (!editor.readonly) {
8466
+ rootNode.find('a').on('click', (event) => {
8467
+ event.preventDefault();
8468
+ });
8469
+ }
8470
+ },
8471
+ };
8472
+
8374
8473
  var copy = (editor) => {
8375
8474
  editor.event.on('copy', event => {
8376
8475
  const range = editor.selection.range;
@@ -9343,11 +9442,12 @@ var formatPainter = (editor) => {
9343
9442
  class LinkPopup {
9344
9443
  constructor(config) {
9345
9444
  this.linkNode = null;
9346
- this.config = config;
9347
- this.root = config.root;
9348
- this.locale = config.locale || i18nObject('en-US');
9445
+ this.scrollListener = () => this.position();
9446
+ this.resizeListener = () => this.position();
9447
+ this.config = config || {};
9448
+ this.locale = this.config.locale || i18nObject('en-US');
9349
9449
  this.container = query(safeTemplate `
9350
- <div class="lake-link-popup">
9450
+ <div class="lake-popup lake-link-popup lake-custom-properties">
9351
9451
  <div class="lake-row">${this.locale.link.url()}</div>
9352
9452
  <div class="lake-row lake-url-row">
9353
9453
  <input type="text" name="url" />
@@ -9545,16 +9645,13 @@ class LinkPopup {
9545
9645
  this.container.css('top', `${linkY + linkRect.height}px`);
9546
9646
  }
9547
9647
  }
9548
- render() {
9549
- this.appendCopyButton();
9550
- this.appendOpenButton();
9551
- this.appendSaveButton();
9552
- this.appendUnlinkButton();
9553
- this.root.append(this.container);
9554
- }
9555
9648
  show(linkNode) {
9556
- if (this.root.find('.lake-link-popup').length === 0) {
9557
- this.render();
9649
+ if (!this.container.get(0).isConnected) {
9650
+ this.appendCopyButton();
9651
+ this.appendOpenButton();
9652
+ this.appendSaveButton();
9653
+ this.appendUnlinkButton();
9654
+ query(document.body).append(this.container);
9558
9655
  }
9559
9656
  if (this.linkNode && this.linkNode.get(0) === linkNode.get(0)) {
9560
9657
  return;
@@ -9571,10 +9668,32 @@ class LinkPopup {
9571
9668
  this.position();
9572
9669
  this.container.css('visibility', '');
9573
9670
  this.container.find('input[name="url"]').focus();
9671
+ const viewport = linkNode.closestScroller();
9672
+ if (viewport.length > 0) {
9673
+ viewport.on('scroll', this.scrollListener);
9674
+ }
9675
+ window.addEventListener('resize', this.resizeListener);
9676
+ if (this.config.onShow) {
9677
+ this.config.onShow();
9678
+ }
9574
9679
  }
9575
9680
  hide() {
9681
+ if (this.linkNode) {
9682
+ const viewport = this.linkNode.closestScroller();
9683
+ if (viewport.length > 0) {
9684
+ viewport.off('scroll', this.scrollListener);
9685
+ }
9686
+ }
9576
9687
  this.linkNode = null;
9577
9688
  this.container.hide();
9689
+ window.removeEventListener('resize', this.resizeListener);
9690
+ if (this.config.onHide) {
9691
+ this.config.onHide();
9692
+ }
9693
+ }
9694
+ unmount() {
9695
+ this.hide();
9696
+ this.container.remove();
9578
9697
  }
9579
9698
  }
9580
9699
 
@@ -9583,7 +9702,6 @@ var link = (editor) => {
9583
9702
  return;
9584
9703
  }
9585
9704
  const popup = new LinkPopup({
9586
- root: editor.popupContainer,
9587
9705
  locale: editor.locale,
9588
9706
  onSave: node => {
9589
9707
  const range = editor.selection.range;
@@ -9599,12 +9717,12 @@ var link = (editor) => {
9599
9717
  editor.selection.sync();
9600
9718
  editor.history.save();
9601
9719
  },
9602
- });
9603
- editor.event.on('scroll', () => {
9604
- popup.position();
9605
- });
9606
- editor.event.on('resize', () => {
9607
- popup.position();
9720
+ onShow: () => {
9721
+ editor.popup = popup;
9722
+ },
9723
+ onHide: () => {
9724
+ editor.popup = null;
9725
+ },
9608
9726
  });
9609
9727
  editor.event.on('click', (targetNode) => {
9610
9728
  if (popup.container.contains(targetNode)) {
@@ -9636,6 +9754,7 @@ var link = (editor) => {
9636
9754
  popup.show(linkNode);
9637
9755
  },
9638
9756
  });
9757
+ return () => popup.unmount();
9639
9758
  };
9640
9759
 
9641
9760
  var hr = (editor) => {
@@ -9827,31 +9946,418 @@ var specialCharacter = (editor) => {
9827
9946
  });
9828
9947
  };
9829
9948
 
9830
- const headingTypeMap = new Map([
9831
- ['#', 'h1'],
9832
- ['##', 'h2'],
9833
- ['###', 'h3'],
9834
- ['####', 'h4'],
9835
- ['#####', 'h5'],
9836
- ['######', 'h6'],
9837
- ]);
9838
- const shortLangTypeMap = new Map([
9839
- ['js', 'javascript'],
9840
- ['ts', 'typescript'],
9841
- ['md', 'markdown'],
9842
- ['htm', 'html'],
9843
- ]);
9844
- const markItemList = [
9845
- {
9846
- re: /\*\*(.+?)\*\*$/,
9847
- getParameters: () => [
9848
- 'bold',
9849
- ],
9850
- },
9851
- {
9852
- re: /__(.+?)__$/,
9853
- getParameters: () => [
9854
- 'bold',
9949
+ const emptyCallback$2 = () => { };
9950
+ class Menu {
9951
+ constructor(config) {
9952
+ this.horizontalDirection = 'right';
9953
+ this.verticalDirection = 'bottom';
9954
+ this.range = null;
9955
+ this.noMouseEvent = false;
9956
+ this.keydownListener = (event) => {
9957
+ if (isKeyHotkey('escape', event)) {
9958
+ event.preventDefault();
9959
+ this.hide();
9960
+ return;
9961
+ }
9962
+ const isDownKey = isKeyHotkey('down', event);
9963
+ const isUpKey = isKeyHotkey('up', event);
9964
+ const isEnterKey = isKeyHotkey('enter', event);
9965
+ if (!isDownKey && !isUpKey && !isEnterKey) {
9966
+ return;
9967
+ }
9968
+ const selectedItemNode = this.container.find('.lake-menu-item-selected');
9969
+ if (selectedItemNode.length === 0) {
9970
+ const firstItem = this.container.find('.lake-menu-item').eq(0);
9971
+ scrollToNode(firstItem, {
9972
+ behavior: 'instant',
9973
+ block: 'start',
9974
+ });
9975
+ firstItem.addClass('lake-menu-item-selected');
9976
+ return;
9977
+ }
9978
+ this.noMouseEvent = true;
9979
+ if (isDownKey) {
9980
+ event.preventDefault();
9981
+ let nextItemNode = selectedItemNode.next();
9982
+ if (nextItemNode.length === 0) {
9983
+ nextItemNode = this.container.find('.lake-menu-item').eq(0);
9984
+ }
9985
+ scrollToNode(nextItemNode, {
9986
+ behavior: 'instant',
9987
+ block: 'end',
9988
+ });
9989
+ this.container.find('.lake-menu-item').removeClass('lake-menu-item-selected');
9990
+ nextItemNode.addClass('lake-menu-item-selected');
9991
+ }
9992
+ else if (isUpKey) {
9993
+ event.preventDefault();
9994
+ let prevItemNode = selectedItemNode.prev();
9995
+ if (prevItemNode.length === 0) {
9996
+ const itemNode = this.container.find('.lake-menu-item');
9997
+ prevItemNode = itemNode.eq(itemNode.length - 1);
9998
+ }
9999
+ scrollToNode(prevItemNode, {
10000
+ behavior: 'instant',
10001
+ block: 'start',
10002
+ });
10003
+ this.container.find('.lake-menu-item').removeClass('lake-menu-item-selected');
10004
+ prevItemNode.addClass('lake-menu-item-selected');
10005
+ }
10006
+ else if (isEnterKey) {
10007
+ event.preventDefault();
10008
+ selectedItemNode.emit('click');
10009
+ }
10010
+ window.setTimeout(() => {
10011
+ this.noMouseEvent = false;
10012
+ }, 50);
10013
+ };
10014
+ this.clickListener = event => {
10015
+ const targetNode = new Nodes(event.target);
10016
+ if (!targetNode.get(0).isConnected) {
10017
+ return;
10018
+ }
10019
+ if (this.container.contains(targetNode)) {
10020
+ return;
10021
+ }
10022
+ this.hide();
10023
+ };
10024
+ this.scrollListener = () => this.updatePosition();
10025
+ this.resizeListener = () => this.updatePosition();
10026
+ this.items = config.items;
10027
+ this.onShow = config.onShow || emptyCallback$2;
10028
+ this.onHide = config.onHide || emptyCallback$2;
10029
+ this.container = query('<ul class="lake-popup lake-menu lake-custom-properties" />');
10030
+ }
10031
+ appendItemNode(itemNode) {
10032
+ itemNode.on('mouseenter', () => {
10033
+ if (this.noMouseEvent) {
10034
+ return;
10035
+ }
10036
+ this.container.find('.lake-menu-item').removeClass('lake-menu-item-selected');
10037
+ itemNode.addClass('lake-menu-item-selected');
10038
+ });
10039
+ itemNode.on('mouseleave', () => {
10040
+ if (this.noMouseEvent) {
10041
+ return;
10042
+ }
10043
+ itemNode.removeClass('lake-menu-item-selected');
10044
+ });
10045
+ this.container.append(itemNode);
10046
+ }
10047
+ appendItems(items) {
10048
+ this.container.empty();
10049
+ for (const item of items) {
10050
+ const itemNode = this.getItemNode(item);
10051
+ itemNode.addClass('lake-menu-item');
10052
+ this.appendItemNode(itemNode);
10053
+ }
10054
+ }
10055
+ selectFirstItemNodeIfNeeded() {
10056
+ const selectedItemNode = this.container.find('.lake-menu-item-selected');
10057
+ if (selectedItemNode.length === 0) {
10058
+ this.container.find('.lake-menu-item').eq(0).addClass('lake-menu-item-selected');
10059
+ }
10060
+ }
10061
+ updatePosition(keepDirection = false) {
10062
+ if (!this.range || this.range.isCollapsed) {
10063
+ return;
10064
+ }
10065
+ const rangeRect = this.range.get().getBoundingClientRect();
10066
+ const rangeX = rangeRect.x + window.scrollX;
10067
+ const rangeY = rangeRect.y + window.scrollY;
10068
+ if (!keepDirection) {
10069
+ if (rangeRect.x + this.container.width() > window.innerWidth) {
10070
+ this.horizontalDirection = 'left';
10071
+ }
10072
+ else {
10073
+ this.horizontalDirection = 'right';
10074
+ }
10075
+ if (rangeRect.y + rangeRect.height + this.container.height() > window.innerHeight) {
10076
+ this.verticalDirection = 'top';
10077
+ }
10078
+ else {
10079
+ this.verticalDirection = 'bottom';
10080
+ }
10081
+ }
10082
+ if (this.horizontalDirection === 'left') {
10083
+ this.container.css('left', `${rangeX - this.container.width() + rangeRect.width}px`);
10084
+ }
10085
+ else {
10086
+ this.container.css('left', `${rangeX}px`);
10087
+ }
10088
+ if (this.verticalDirection === 'top') {
10089
+ this.container.css('top', `${rangeY - this.container.height() - 5}px`);
10090
+ }
10091
+ else {
10092
+ this.container.css('top', `${rangeY + rangeRect.height + 5}px`);
10093
+ }
10094
+ }
10095
+ get visible() {
10096
+ return this.container.get(0).isConnected && this.container.computedCSS('display') !== 'none';
10097
+ }
10098
+ update(keyword) {
10099
+ const items = this.search(keyword);
10100
+ if (items.length === 0) {
10101
+ this.hide();
10102
+ return;
10103
+ }
10104
+ this.appendItems(items);
10105
+ this.selectFirstItemNodeIfNeeded();
10106
+ this.updatePosition(true);
10107
+ this.container.css('visibility', '');
10108
+ }
10109
+ show(range, keyword) {
10110
+ if (range.isCollapsed) {
10111
+ return;
10112
+ }
10113
+ if (this.items.length === 0) {
10114
+ return;
10115
+ }
10116
+ if (!this.container.get(0).isConnected) {
10117
+ query(document.body).append(this.container);
10118
+ }
10119
+ // append all items for fixing the container's width
10120
+ this.appendItems(this.items);
10121
+ this.selectFirstItemNodeIfNeeded();
10122
+ this.range = range;
10123
+ this.container.css('visibility', 'hidden');
10124
+ this.container.show();
10125
+ this.container.get(0).scrollTo(0, 0);
10126
+ this.updatePosition();
10127
+ // fix the container's width
10128
+ this.container.css('width', '');
10129
+ this.container.css('width', `${this.container.width()}px`);
10130
+ const viewport = range.commonAncestor.closestScroller();
10131
+ if (viewport.length > 0) {
10132
+ viewport.on('scroll', this.scrollListener);
10133
+ }
10134
+ document.addEventListener('keydown', this.keydownListener, true);
10135
+ document.addEventListener('click', this.clickListener);
10136
+ window.addEventListener('resize', this.resizeListener);
10137
+ if (keyword) {
10138
+ const items = this.search(keyword);
10139
+ if (items.length === 0) {
10140
+ return;
10141
+ }
10142
+ this.appendItems(items);
10143
+ this.selectFirstItemNodeIfNeeded();
10144
+ this.updatePosition(true);
10145
+ }
10146
+ this.container.css('visibility', '');
10147
+ this.onShow();
10148
+ }
10149
+ hide() {
10150
+ if (this.range) {
10151
+ const viewport = this.range.commonAncestor.closestScroller();
10152
+ if (viewport.length > 0) {
10153
+ viewport.off('scroll', this.scrollListener);
10154
+ }
10155
+ }
10156
+ this.range = null;
10157
+ this.container.hide();
10158
+ document.removeEventListener('keydown', this.keydownListener, true);
10159
+ document.removeEventListener('click', this.clickListener);
10160
+ window.removeEventListener('resize', this.resizeListener);
10161
+ this.onHide();
10162
+ }
10163
+ unmount() {
10164
+ this.hide();
10165
+ this.container.remove();
10166
+ }
10167
+ }
10168
+
10169
+ const emptyCallback$1 = () => { };
10170
+ class MentionMenu extends Menu {
10171
+ constructor(config) {
10172
+ super(config);
10173
+ this.onSelect = config.onSelect || emptyCallback$1;
10174
+ this.container.addClass('lake-mention-menu');
10175
+ }
10176
+ getItemNode(item) {
10177
+ var _a;
10178
+ const itemNode = query(safeTemplate `
10179
+ <li>
10180
+ <div class="lake-mention-avatar"></div>
10181
+ <div class="lake-mention-nickname">${(_a = item.nickname) !== null && _a !== void 0 ? _a : item.name}</div>
10182
+ <div class="lake-mention-name">(${item.name})</div>
10183
+ </li>
10184
+ `);
10185
+ const avatarNode = itemNode.find('.lake-mention-avatar');
10186
+ if (item.avatar) {
10187
+ avatarNode.append(item.avatar);
10188
+ }
10189
+ else {
10190
+ avatarNode.remove();
10191
+ }
10192
+ if (!item.nickname) {
10193
+ itemNode.find('.lake-mention-name').remove();
10194
+ }
10195
+ itemNode.on('click', event => this.onSelect(event, item));
10196
+ return itemNode;
10197
+ }
10198
+ search(keyword) {
10199
+ var _a;
10200
+ keyword = keyword.toLowerCase();
10201
+ const items = [];
10202
+ for (const item of this.items) {
10203
+ const nickname = (_a = item.nickname) !== null && _a !== void 0 ? _a : item.name;
10204
+ if (item.name.toLowerCase().indexOf(keyword) >= 0 ||
10205
+ nickname.toLowerCase().indexOf(keyword) >= 0 ||
10206
+ nickname.replace(/\s+/g, '').indexOf(keyword) >= 0) {
10207
+ items.push(item);
10208
+ }
10209
+ }
10210
+ return items;
10211
+ }
10212
+ }
10213
+
10214
+ function getKeyword$1(range) {
10215
+ const targetRange = range.getCharacterRange('@');
10216
+ if (targetRange === null) {
10217
+ return null;
10218
+ }
10219
+ let text = targetRange.startNode.text().slice(targetRange.startOffset + 1, targetRange.endOffset);
10220
+ text = text.replace(/[\u200B\u2060]/g, '');
10221
+ return text;
10222
+ }
10223
+ var mention = (editor) => {
10224
+ editor.setPluginConfig('mention', {
10225
+ requestMethod: 'GET',
10226
+ items: [],
10227
+ getProfileUrl: (value) => `/${value.name}`,
10228
+ });
10229
+ if (editor.readonly) {
10230
+ return;
10231
+ }
10232
+ const { requestAction, requestMethod, items } = editor.config.mention;
10233
+ let menu = null;
10234
+ const selectListener = (event, item) => {
10235
+ if (menu) {
10236
+ menu.hide();
10237
+ }
10238
+ editor.focus();
10239
+ const targetRange = editor.selection.range.getCharacterRange('@');
10240
+ if (targetRange) {
10241
+ targetRange.get().deleteContents();
10242
+ }
10243
+ editor.selection.insertBox('mention', item);
10244
+ editor.history.save();
10245
+ };
10246
+ const showListener = () => {
10247
+ editor.popup = menu;
10248
+ };
10249
+ const hideListener = () => {
10250
+ editor.popup = null;
10251
+ };
10252
+ const showMenu = () => {
10253
+ const range = editor.selection.range;
10254
+ if (!range.isCollapsed) {
10255
+ return;
10256
+ }
10257
+ const targetRange = range.getCharacterRange('@');
10258
+ if (targetRange === null) {
10259
+ return;
10260
+ }
10261
+ const keyword = getKeyword$1(range);
10262
+ if (keyword === null) {
10263
+ return;
10264
+ }
10265
+ if (!menu) {
10266
+ if (requestAction) {
10267
+ request({
10268
+ onSuccess: body => {
10269
+ if (!body.data) {
10270
+ return;
10271
+ }
10272
+ menu = new MentionMenu({
10273
+ items: body.data,
10274
+ onSelect: selectListener,
10275
+ onShow: showListener,
10276
+ onHide: hideListener,
10277
+ });
10278
+ menu.show(targetRange, keyword);
10279
+ },
10280
+ action: requestAction,
10281
+ method: requestMethod,
10282
+ });
10283
+ }
10284
+ else {
10285
+ menu = new MentionMenu({
10286
+ items,
10287
+ onSelect: selectListener,
10288
+ onShow: showListener,
10289
+ onHide: hideListener,
10290
+ });
10291
+ menu.show(targetRange, keyword);
10292
+ }
10293
+ return;
10294
+ }
10295
+ menu.show(targetRange, keyword);
10296
+ };
10297
+ editor.container.on('keyup', event => {
10298
+ if (editor.isComposing) {
10299
+ return;
10300
+ }
10301
+ const keyboardEvent = event;
10302
+ if (isKeyHotkey(['down', 'up', 'enter'], keyboardEvent)) {
10303
+ return;
10304
+ }
10305
+ if (!menu || !menu.visible) {
10306
+ if (keyboardEvent.key === '@') {
10307
+ showMenu();
10308
+ return;
10309
+ }
10310
+ if (isKeyHotkey(['backspace', 'delete'], keyboardEvent)) {
10311
+ showMenu();
10312
+ }
10313
+ else {
10314
+ return;
10315
+ }
10316
+ }
10317
+ const range = editor.selection.range;
10318
+ const keyword = getKeyword$1(range);
10319
+ if (keyword === null) {
10320
+ if (menu) {
10321
+ menu.hide();
10322
+ }
10323
+ return;
10324
+ }
10325
+ if (menu) {
10326
+ menu.update(keyword);
10327
+ }
10328
+ });
10329
+ return () => {
10330
+ if (menu) {
10331
+ menu.unmount();
10332
+ }
10333
+ };
10334
+ };
10335
+
10336
+ const headingTypeMap = new Map([
10337
+ ['#', 'h1'],
10338
+ ['##', 'h2'],
10339
+ ['###', 'h3'],
10340
+ ['####', 'h4'],
10341
+ ['#####', 'h5'],
10342
+ ['######', 'h6'],
10343
+ ]);
10344
+ const shortLangTypeMap = new Map([
10345
+ ['js', 'javascript'],
10346
+ ['ts', 'typescript'],
10347
+ ['md', 'markdown'],
10348
+ ['htm', 'html'],
10349
+ ]);
10350
+ const markItemList = [
10351
+ {
10352
+ re: /\*\*(.+?)\*\*$/,
10353
+ getParameters: () => [
10354
+ 'bold',
10355
+ ],
10356
+ },
10357
+ {
10358
+ re: /__(.+?)__$/,
10359
+ getParameters: () => [
10360
+ 'bold',
9855
10361
  ],
9856
10362
  },
9857
10363
  {
@@ -10947,87 +11453,17 @@ const slashItems = [
10947
11453
  },
10948
11454
  ];
10949
11455
 
11456
+ const emptyCallback = () => { };
10950
11457
  const slashItemMap = new Map();
10951
11458
  for (const item of slashItems) {
10952
11459
  slashItemMap.set(item.name, item);
10953
11460
  }
10954
- class SlashPopup {
11461
+ class SlashMenu extends Menu {
10955
11462
  constructor(config) {
10956
- this.range = null;
10957
- this.noMouseEvent = false;
10958
- this.keyword = null;
10959
- this.horizontalDirection = 'right';
10960
- this.verticalDirection = 'bottom';
10961
- this.keydownListener = (event) => {
10962
- if (isKeyHotkey('escape', event)) {
10963
- event.preventDefault();
10964
- this.hide();
10965
- return;
10966
- }
10967
- const isDownKey = isKeyHotkey('down', event);
10968
- const isUpKey = isKeyHotkey('up', event);
10969
- const isEnterKey = isKeyHotkey('enter', event);
10970
- if (!isDownKey && !isUpKey && !isEnterKey) {
10971
- return;
10972
- }
10973
- const selectedItemNode = this.container.find('.lake-slash-item-selected');
10974
- if (selectedItemNode.length === 0) {
10975
- const firstItem = this.container.find('.lake-slash-item').eq(0);
10976
- scrollToNode(firstItem, {
10977
- behavior: 'instant',
10978
- block: 'start',
10979
- });
10980
- firstItem.addClass('lake-slash-item-selected');
10981
- return;
10982
- }
10983
- this.noMouseEvent = true;
10984
- if (isDownKey) {
10985
- event.preventDefault();
10986
- let nextItemNode = selectedItemNode.next();
10987
- if (nextItemNode.length === 0) {
10988
- nextItemNode = this.container.find('.lake-slash-item').eq(0);
10989
- }
10990
- scrollToNode(nextItemNode, {
10991
- behavior: 'instant',
10992
- block: 'end',
10993
- });
10994
- this.container.find('.lake-slash-item').removeClass('lake-slash-item-selected');
10995
- nextItemNode.addClass('lake-slash-item-selected');
10996
- }
10997
- else if (isUpKey) {
10998
- event.preventDefault();
10999
- let prevItemNode = selectedItemNode.prev();
11000
- if (prevItemNode.length === 0) {
11001
- const itemNode = this.container.find('.lake-slash-item');
11002
- prevItemNode = itemNode.eq(itemNode.length - 1);
11003
- }
11004
- scrollToNode(prevItemNode, {
11005
- behavior: 'instant',
11006
- block: 'start',
11007
- });
11008
- this.container.find('.lake-slash-item').removeClass('lake-slash-item-selected');
11009
- prevItemNode.addClass('lake-slash-item-selected');
11010
- }
11011
- else if (isEnterKey) {
11012
- event.preventDefault();
11013
- selectedItemNode.emit('click');
11014
- }
11015
- window.setTimeout(() => {
11016
- this.noMouseEvent = false;
11017
- }, 50);
11018
- };
11019
- this.clickListener = (targetNode) => {
11020
- if (this.container.contains(targetNode)) {
11021
- return;
11022
- }
11023
- this.hide();
11024
- };
11025
- this.scrollListener = () => this.position();
11026
- this.resizeListener = () => this.position();
11027
- this.editor = config.editor;
11028
- this.items = config.items;
11029
- this.root = config.editor.popupContainer;
11030
- this.container = query('<ul class="lake-slash-popup" />');
11463
+ super(config);
11464
+ this.locale = config.locale || i18nObject('en-US');
11465
+ this.onSelect = config.onSelect || emptyCallback;
11466
+ this.container.addClass('lake-slash-menu');
11031
11467
  }
11032
11468
  getItem(name) {
11033
11469
  if (typeof name !== 'string') {
@@ -11039,20 +11475,12 @@ class SlashPopup {
11039
11475
  }
11040
11476
  return item;
11041
11477
  }
11042
- emptyBlock() {
11043
- const range = this.editor.selection.range;
11044
- const block = range.commonAncestor.closestBlock();
11045
- this.hide();
11046
- block.empty();
11047
- appendBreak(block);
11048
- range.shrinkBefore(block);
11049
- }
11050
- appendItem(item) {
11051
- const editor = this.editor;
11052
- const itemTitle = typeof item.title === 'string' ? item.title : item.title(editor.locale);
11053
- const itemDescription = typeof item.description === 'string' ? item.description : item.description(editor.locale);
11478
+ getItemNode(name) {
11479
+ const item = this.getItem(name);
11480
+ const itemTitle = typeof item.title === 'string' ? item.title : item.title(this.locale);
11481
+ const itemDescription = typeof item.description === 'string' ? item.description : item.description(this.locale);
11054
11482
  const itemNode = query(safeTemplate `
11055
- <li class="lake-slash-item" name="${item.name}">
11483
+ <li name="${item.name}">
11056
11484
  <div class="lake-slash-icon"></div>
11057
11485
  <div class="lake-slash-text">
11058
11486
  <div class="lake-slash-title">${itemTitle}</div>
@@ -11064,20 +11492,6 @@ class SlashPopup {
11064
11492
  if (icon) {
11065
11493
  itemNode.find('.lake-slash-icon').append(icon);
11066
11494
  }
11067
- this.container.append(itemNode);
11068
- itemNode.on('mouseenter', () => {
11069
- if (this.noMouseEvent) {
11070
- return;
11071
- }
11072
- this.container.find('.lake-slash-item').removeClass('lake-slash-item-selected');
11073
- itemNode.addClass('lake-slash-item-selected');
11074
- });
11075
- itemNode.on('mouseleave', () => {
11076
- if (this.noMouseEvent) {
11077
- return;
11078
- }
11079
- itemNode.removeClass('lake-slash-item-selected');
11080
- });
11081
11495
  if (item.type === 'upload') {
11082
11496
  itemNode.append('<input type="file" />');
11083
11497
  const fileNode = itemNode.find('input[type="file"]');
@@ -11089,49 +11503,21 @@ class SlashPopup {
11089
11503
  fileNode.attr('multiple', 'true');
11090
11504
  }
11091
11505
  fileNode.on('click', event => event.stopPropagation());
11092
- fileNode.on('change', event => {
11093
- editor.focus();
11094
- this.emptyBlock();
11095
- const target = event.target;
11096
- const files = target.files || [];
11097
- for (const file of files) {
11098
- uploadFile({
11099
- editor,
11100
- name: item.name,
11101
- file,
11102
- onError: error => {
11103
- fileNativeNode.value = '';
11104
- editor.config.onMessage('error', error);
11105
- },
11106
- onSuccess: () => {
11107
- fileNativeNode.value = '';
11108
- },
11109
- });
11110
- }
11111
- });
11112
- itemNode.on('click', () => {
11113
- fileNativeNode.click();
11114
- });
11506
+ fileNode.on('change', event => this.onSelect(event, item, fileNode));
11507
+ itemNode.on('click', () => fileNativeNode.click());
11115
11508
  }
11116
11509
  else {
11117
- itemNode.on('click', () => {
11118
- editor.focus();
11119
- this.emptyBlock();
11120
- item.onClick(editor, item.name);
11121
- });
11510
+ itemNode.on('click', event => this.onSelect(event, item));
11122
11511
  }
11123
- }
11124
- get visible() {
11125
- return this.container.get(0).isConnected && this.container.computedCSS('display') !== 'none';
11512
+ return itemNode;
11126
11513
  }
11127
11514
  search(keyword) {
11128
- const editor = this.editor;
11129
11515
  const localeEnglish = i18nObject('en-US');
11130
11516
  keyword = keyword.toLowerCase();
11131
11517
  const items = [];
11132
11518
  for (const name of this.items) {
11133
11519
  const item = this.getItem(name);
11134
- let itemTitle = typeof item.title === 'string' ? item.title : item.title(editor.locale);
11520
+ let itemTitle = typeof item.title === 'string' ? item.title : item.title(this.locale);
11135
11521
  itemTitle = itemTitle.toLowerCase();
11136
11522
  let itemTitleEnglish = typeof item.title === 'string' ? item.title : item.title(localeEnglish);
11137
11523
  itemTitleEnglish = itemTitleEnglish.toLowerCase();
@@ -11144,103 +11530,6 @@ class SlashPopup {
11144
11530
  }
11145
11531
  return items;
11146
11532
  }
11147
- position(keepDirection = false) {
11148
- if (!this.range) {
11149
- return;
11150
- }
11151
- this.container.css('visibility', '');
11152
- const rangeRect = this.range.get().getBoundingClientRect();
11153
- const rangeX = rangeRect.x + window.scrollX;
11154
- const rangeY = rangeRect.y + window.scrollY;
11155
- if (!keepDirection) {
11156
- if (rangeRect.x + this.container.width() > window.innerWidth) {
11157
- this.horizontalDirection = 'left';
11158
- }
11159
- else {
11160
- this.horizontalDirection = 'right';
11161
- }
11162
- if (rangeRect.y + rangeRect.height + this.container.height() > window.innerHeight) {
11163
- this.verticalDirection = 'top';
11164
- }
11165
- else {
11166
- this.verticalDirection = 'bottom';
11167
- }
11168
- }
11169
- if (this.horizontalDirection === 'left') {
11170
- this.container.css('left', `${rangeX - this.container.width() + rangeRect.width}px`);
11171
- }
11172
- else {
11173
- this.container.css('left', `${rangeX}px`);
11174
- }
11175
- if (this.verticalDirection === 'top') {
11176
- this.container.css('top', `${rangeY - this.container.height() - 5}px`);
11177
- }
11178
- else {
11179
- this.container.css('top', `${rangeY + rangeRect.height + 5}px`);
11180
- }
11181
- }
11182
- render() {
11183
- this.root.append(this.container);
11184
- this.update();
11185
- }
11186
- update(keyword = null) {
11187
- if (keyword !== null && this.keyword === keyword) {
11188
- return;
11189
- }
11190
- const items = keyword !== null ? this.search(keyword) : this.items;
11191
- if (items.length === 0) {
11192
- this.hide();
11193
- return;
11194
- }
11195
- this.keyword = keyword;
11196
- this.container.empty();
11197
- for (const name of items) {
11198
- const item = this.getItem(name);
11199
- this.appendItem(item);
11200
- }
11201
- const selectedItemNode = this.container.find('.lake-slash-item-selected');
11202
- if (selectedItemNode.length === 0) {
11203
- this.container.find('.lake-slash-item').eq(0).addClass('lake-slash-item-selected');
11204
- }
11205
- this.position(true);
11206
- }
11207
- show(range, keyword) {
11208
- const editor = this.editor;
11209
- if (this.root.find('.lake-slash-popup').length === 0) {
11210
- this.render();
11211
- }
11212
- else {
11213
- this.update();
11214
- }
11215
- this.range = range;
11216
- this.container.css('visibility', 'hidden');
11217
- this.container.show();
11218
- this.position();
11219
- // for fixing the container's width
11220
- this.container.css('width', '');
11221
- this.container.css('width', `${this.container.width()}px`);
11222
- if (keyword) {
11223
- this.update(keyword);
11224
- }
11225
- this.container.css('visibility', '');
11226
- document.addEventListener('keydown', this.keydownListener, true);
11227
- editor.event.on('click', this.clickListener);
11228
- editor.event.on('scroll', this.scrollListener);
11229
- editor.event.on('resize', this.resizeListener);
11230
- }
11231
- hide() {
11232
- const editor = this.editor;
11233
- this.range = null;
11234
- this.container.hide();
11235
- document.removeEventListener('keydown', this.keydownListener, true);
11236
- editor.event.off('click', this.clickListener);
11237
- editor.event.off('scroll', this.scrollListener);
11238
- editor.event.off('resize', this.resizeListener);
11239
- }
11240
- unmount() {
11241
- this.hide();
11242
- this.container.remove();
11243
- }
11244
11533
  }
11245
11534
 
11246
11535
  const defaultItems = [
@@ -11265,25 +11554,12 @@ function getKeyword(block) {
11265
11554
  }
11266
11555
  return text.substring(1);
11267
11556
  }
11268
- function showPopup(editor, popup) {
11557
+ function emptyBlock(editor) {
11269
11558
  const range = editor.selection.range;
11270
- if (!range.isCollapsed) {
11271
- return;
11272
- }
11273
- const block = range.getBlocks()[0];
11274
- if (!block) {
11275
- return;
11276
- }
11277
- if (block.find('lake-box').length > 0) {
11278
- return;
11279
- }
11280
- const keyword = getKeyword(block);
11281
- if (keyword === null) {
11282
- return;
11283
- }
11284
- const slashRange = range.clone();
11285
- slashRange.selectNodeContents(block);
11286
- popup.show(slashRange, keyword);
11559
+ const block = range.commonAncestor.closestBlock();
11560
+ block.empty();
11561
+ appendBreak(block);
11562
+ range.shrinkBefore(block);
11287
11563
  }
11288
11564
  var slash = (editor) => {
11289
11565
  editor.setPluginConfig('slash', {
@@ -11292,10 +11568,68 @@ var slash = (editor) => {
11292
11568
  if (editor.readonly) {
11293
11569
  return;
11294
11570
  }
11295
- const popup = new SlashPopup({
11296
- editor,
11571
+ const menu = new SlashMenu({
11572
+ locale: editor.locale,
11297
11573
  items: editor.config.slash.items,
11574
+ onSelect: (event, item, fileNode) => {
11575
+ if (menu) {
11576
+ menu.hide();
11577
+ }
11578
+ editor.focus();
11579
+ emptyBlock(editor);
11580
+ if (item.type === 'upload') {
11581
+ if (!fileNode) {
11582
+ return;
11583
+ }
11584
+ const target = event.target;
11585
+ const fileNativeNode = fileNode.get(0);
11586
+ const files = target.files || [];
11587
+ for (const file of files) {
11588
+ uploadFile({
11589
+ editor,
11590
+ name: item.name,
11591
+ file,
11592
+ onError: error => {
11593
+ fileNativeNode.value = '';
11594
+ editor.config.onMessage('error', error);
11595
+ },
11596
+ onSuccess: () => {
11597
+ fileNativeNode.value = '';
11598
+ },
11599
+ });
11600
+ }
11601
+ }
11602
+ else {
11603
+ item.onClick(editor, item.name);
11604
+ }
11605
+ },
11606
+ onShow: () => {
11607
+ editor.popup = menu;
11608
+ },
11609
+ onHide: () => {
11610
+ editor.popup = null;
11611
+ },
11298
11612
  });
11613
+ const showMenu = () => {
11614
+ const range = editor.selection.range;
11615
+ if (!range.isCollapsed) {
11616
+ return;
11617
+ }
11618
+ const block = range.getBlocks()[0];
11619
+ if (!block) {
11620
+ return;
11621
+ }
11622
+ if (block.find('lake-box').length > 0) {
11623
+ return;
11624
+ }
11625
+ const keyword = getKeyword(block);
11626
+ if (keyword === null) {
11627
+ return;
11628
+ }
11629
+ const slashRange = range.clone();
11630
+ slashRange.selectNodeContents(block);
11631
+ menu.show(slashRange, keyword);
11632
+ };
11299
11633
  editor.container.on('keyup', event => {
11300
11634
  if (editor.isComposing) {
11301
11635
  return;
@@ -11304,13 +11638,13 @@ var slash = (editor) => {
11304
11638
  if (isKeyHotkey(['down', 'up', 'enter'], keyboardEvent)) {
11305
11639
  return;
11306
11640
  }
11307
- if (!popup.visible) {
11641
+ if (!menu.visible) {
11308
11642
  if (isKeyHotkey('/', keyboardEvent)) {
11309
- showPopup(editor, popup);
11643
+ showMenu();
11310
11644
  return;
11311
11645
  }
11312
11646
  if (isKeyHotkey(['backspace', 'delete'], keyboardEvent)) {
11313
- showPopup(editor, popup);
11647
+ showMenu();
11314
11648
  }
11315
11649
  else {
11316
11650
  return;
@@ -11323,12 +11657,14 @@ var slash = (editor) => {
11323
11657
  }
11324
11658
  const keyword = getKeyword(block);
11325
11659
  if (keyword === null) {
11326
- popup.hide();
11660
+ menu.hide();
11327
11661
  return;
11328
11662
  }
11329
- popup.update(keyword);
11663
+ menu.update(keyword);
11330
11664
  });
11331
- return () => popup.unmount();
11665
+ return () => {
11666
+ menu.unmount();
11667
+ };
11332
11668
  };
11333
11669
 
11334
11670
  Editor.box.add(hrBox);
@@ -11338,6 +11674,7 @@ Editor.box.add(videoBox);
11338
11674
  Editor.box.add(fileBox);
11339
11675
  Editor.box.add(emojiBox);
11340
11676
  Editor.box.add(equationBox);
11677
+ Editor.box.add(mentionBox);
11341
11678
  Editor.plugin.add('copy', copy);
11342
11679
  Editor.plugin.add('cut', cut);
11343
11680
  Editor.plugin.add('paste', paste);
@@ -11372,6 +11709,7 @@ Editor.plugin.add('file', file);
11372
11709
  Editor.plugin.add('emoji', emoji);
11373
11710
  Editor.plugin.add('equation', equation);
11374
11711
  Editor.plugin.add('specialCharacter', specialCharacter);
11712
+ Editor.plugin.add('mention', mention);
11375
11713
  Editor.plugin.add('markdown', markdown);
11376
11714
  Editor.plugin.add('enterKey', enterKey);
11377
11715
  Editor.plugin.add('shiftEnterKey', shiftEnterKey);