lakelib 0.3.9 → 0.3.11

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
@@ -1,8 +1,8 @@
1
1
  import EventEmitter from 'eventemitter3';
2
2
  import { i18nObject as i18nObject$1 } from 'typesafe-i18n';
3
+ import { isKeyHotkey } from 'is-hotkey';
3
4
  import debounce from 'debounce';
4
5
  import isEqual from 'fast-deep-equal/es6';
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';
@@ -149,6 +149,8 @@ var specialCharacter$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\"
149
149
 
150
150
  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>";
151
151
 
152
+ var twitter$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M214.75,211.71l-62.6-98.38,61.77-67.95a8,8,0,0,0-11.84-10.76L143.24,99.34,102.75,35.71A8,8,0,0,0,96,32H48a8,8,0,0,0-6.75,12.3l62.6,98.37-61.77,68a8,8,0,1,0,11.84,10.76l58.84-64.72,40.49,63.63A8,8,0,0,0,160,224h48a8,8,0,0,0,6.75-12.29ZM164.39,208,62.57,48h29L193.43,208Z\"></path></svg>";
153
+
152
154
  var table$1 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM104,144V112h48v32Zm48,16v32H104V160ZM40,112H88v32H40Zm64-16V64h48V96Zm64,16h48v32H168Zm48-16H168V64h48ZM88,64V96H40V64ZM40,160H88v32H40Zm176,32H168V160h48v32Z\"></path></svg>";
153
155
 
154
156
  var unlink = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"#000000\" viewBox=\"0 0 256 256\"><path d=\"M190.63,65.37a32,32,0,0,0-45.19-.06L133.79,77.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,190.63,65.37ZM122.21,178.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L54.19,133.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM208,152H184a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM48,104H72a8,8,0,0,0,0-16H48a8,8,0,0,0,0,16Zm112,72a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V184A8,8,0,0,0,160,176ZM96,80a8,8,0,0,0,8-8V48a8,8,0,0,0-16,0V72A8,8,0,0,0,96,80Z\"></path></svg>";
@@ -263,6 +265,7 @@ const icons = new Map([
263
265
  ['emoji', emoji$1],
264
266
  ['specialCharacter', specialCharacter$1],
265
267
  ['equation', equation$1],
268
+ ['twitter', twitter$1],
266
269
  ['table', table$1],
267
270
  // link
268
271
  ['unlink', unlink],
@@ -1265,15 +1268,10 @@ class Nodes {
1265
1268
  var _a;
1266
1269
  if (value === undefined) {
1267
1270
  const node = this.get(0);
1268
- if (this.isText) {
1269
- return (_a = node.nodeValue) !== null && _a !== void 0 ? _a : '';
1270
- }
1271
- const element = node;
1272
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText
1273
- return element.innerText.replace(/^\n+|\n+$/, '');
1271
+ return (_a = node.textContent) !== null && _a !== void 0 ? _a : '';
1274
1272
  }
1275
1273
  return this.eachElement(element => {
1276
- element.innerText = value;
1274
+ element.textContent = value;
1277
1275
  });
1278
1276
  }
1279
1277
  value(value) {
@@ -2864,6 +2862,15 @@ const toolbarItems = [
2864
2862
  editor.command.execute(value);
2865
2863
  },
2866
2864
  },
2865
+ {
2866
+ name: 'twitter',
2867
+ type: 'button',
2868
+ icon: icons.get('twitter'),
2869
+ tooltip: locale => locale.toolbar.twitter(),
2870
+ onClick: (editor, value) => {
2871
+ editor.command.execute(value);
2872
+ },
2873
+ },
2867
2874
  {
2868
2875
  name: 'heading',
2869
2876
  type: 'dropdown',
@@ -3092,12 +3099,12 @@ function request(option) {
3092
3099
  formData.append(key, value);
3093
3100
  });
3094
3101
  if (option.file) {
3095
- const filename = option.filename || 'file';
3102
+ const fieldName = option.fieldName || 'file';
3096
3103
  if (option.file instanceof Blob) {
3097
- formData.append(filename, option.file, option.file.name);
3104
+ formData.append(fieldName, option.file, option.file.name);
3098
3105
  }
3099
3106
  else {
3100
- formData.append(filename, option.file);
3107
+ formData.append(fieldName, option.file);
3101
3108
  }
3102
3109
  }
3103
3110
  xhr.onerror = e => {
@@ -3139,18 +3146,19 @@ function request(option) {
3139
3146
  return xhr;
3140
3147
  }
3141
3148
 
3142
- // Uploads a file to the server.
3143
- function uploadFile(config) {
3144
- const { editor, name, file, onError, onSuccess } = config;
3145
- const { requestMethod, requestAction, requestTypes } = editor.config[name];
3149
+ /**
3150
+ * Inserts an upload box into a given selection and sends an HTTP request to upload the file to a specified URL.
3151
+ */
3152
+ function insertUploadBox(config) {
3153
+ const { selection, boxName, file, requestTypes, requestMethod, requestAction, requestFieldName, requestWithCredentials, requestHeaders, transformResponse, onError, onSuccess, } = config;
3146
3154
  if (requestTypes.indexOf(file.type) < 0) {
3147
3155
  if (onError) {
3148
3156
  onError(`File "${file.name}" is not allowed for uploading.`);
3149
3157
  }
3150
3158
  throw new Error(`Cannot upload file "${file.name}" because its type "${file.type}" is not found in ['${requestTypes.join('\', \'')}'].`);
3151
3159
  }
3152
- const box = editor.selection.insertBox(name, {
3153
- url: URL.createObjectURL(file),
3160
+ const box = selection.insertBox(boxName, {
3161
+ url: file.type.indexOf('image/') === 0 ? URL.createObjectURL(file) : '',
3154
3162
  status: 'uploading',
3155
3163
  name: file.name,
3156
3164
  size: file.size,
@@ -3174,6 +3182,9 @@ function uploadFile(config) {
3174
3182
  },
3175
3183
  onSuccess: body => {
3176
3184
  xhr = null;
3185
+ if (transformResponse) {
3186
+ body = transformResponse(body);
3187
+ }
3177
3188
  if (!body.url) {
3178
3189
  box.updateValue('status', 'error');
3179
3190
  box.render();
@@ -3187,7 +3198,6 @@ function uploadFile(config) {
3187
3198
  url: body.url,
3188
3199
  });
3189
3200
  box.render();
3190
- editor.history.save();
3191
3201
  if (onSuccess) {
3192
3202
  onSuccess();
3193
3203
  }
@@ -3195,6 +3205,9 @@ function uploadFile(config) {
3195
3205
  file,
3196
3206
  action: requestAction,
3197
3207
  method: requestMethod,
3208
+ fieldName: requestFieldName,
3209
+ withCredentials: requestWithCredentials,
3210
+ headers: requestHeaders,
3198
3211
  });
3199
3212
  box.event.on('beforeunmount', () => {
3200
3213
  if (xhr) {
@@ -3318,7 +3331,7 @@ var enUS = {
3318
3331
  formatPainter: 'Format painter',
3319
3332
  link: 'Link',
3320
3333
  hr: 'Horizontal line',
3321
- video: 'Video',
3334
+ video: 'YouTube',
3322
3335
  codeBlock: 'Code block',
3323
3336
  heading: 'Heading',
3324
3337
  heading1: 'Heading 1',
@@ -3340,6 +3353,7 @@ var enUS = {
3340
3353
  file: 'File',
3341
3354
  emoji: 'Emoji',
3342
3355
  equation: 'Mathematical formula',
3356
+ twitter: 'X (Tweet)',
3343
3357
  removeColor: 'Remove color',
3344
3358
  },
3345
3359
  slash: {
@@ -3379,10 +3393,12 @@ var enUS = {
3379
3393
  hrDesc: 'Insert a horizontal line',
3380
3394
  codeBlock: 'Code block',
3381
3395
  codeBlockDesc: 'Insert a code block',
3382
- video: 'Video',
3383
- videoDesc: 'Insert a video from YouTube',
3396
+ video: 'YouTube',
3397
+ videoDesc: 'Insert a YouTube video',
3384
3398
  equation: 'Mathematical formula',
3385
3399
  equationDesc: 'Insert a TeX expression',
3400
+ twitter: 'Tweet',
3401
+ twitterDesc: 'Insert an X (Tweet)',
3386
3402
  image: 'Image',
3387
3403
  imageDesc: 'Upload an image',
3388
3404
  file: 'File',
@@ -3446,7 +3462,7 @@ var enUS = {
3446
3462
  video: {
3447
3463
  embed: 'Embed video',
3448
3464
  remove: 'Delete',
3449
- description: 'Paste a link to embed a video from YouTube.',
3465
+ description: 'Paste a YouTube link to embed the video.',
3450
3466
  url: 'Link',
3451
3467
  urlError: 'Please enter a valid link.',
3452
3468
  },
@@ -3458,6 +3474,13 @@ var enUS = {
3458
3474
  help: 'Supported functions',
3459
3475
  placeholder: 'Type a TeX expression...',
3460
3476
  },
3477
+ twitter: {
3478
+ embed: 'Embed Tweet',
3479
+ remove: 'Delete',
3480
+ description: 'Paste an X (Twitter) link to embed the post.',
3481
+ url: 'Link',
3482
+ urlError: 'Please enter a valid link.',
3483
+ },
3461
3484
  };
3462
3485
 
3463
3486
  var zhCN = {
@@ -3487,7 +3510,7 @@ var zhCN = {
3487
3510
  formatPainter: '格式刷',
3488
3511
  link: '链接',
3489
3512
  hr: '分割线',
3490
- video: '视频',
3513
+ video: 'YouTube',
3491
3514
  codeBlock: '代码块',
3492
3515
  heading: '标题',
3493
3516
  heading1: '标题 1',
@@ -3509,6 +3532,7 @@ var zhCN = {
3509
3532
  file: '文件',
3510
3533
  emoji: '表情',
3511
3534
  equation: '数学公式',
3535
+ twitter: 'X (Tweet)',
3512
3536
  removeColor: '默认',
3513
3537
  },
3514
3538
  slash: {
@@ -3548,10 +3572,12 @@ var zhCN = {
3548
3572
  hrDesc: '插入分割线',
3549
3573
  codeBlock: '代码块',
3550
3574
  codeBlockDesc: '插入代码块',
3551
- video: '视频',
3575
+ video: 'YouTube',
3552
3576
  videoDesc: '插入 YouTube 视频',
3553
3577
  equation: '数学公式',
3554
3578
  equationDesc: '支持 TeX 语法',
3579
+ twitter: 'Tweet',
3580
+ twitterDesc: '插入 Tweet',
3555
3581
  image: '图片',
3556
3582
  imageDesc: '上传图片',
3557
3583
  file: '文件',
@@ -3627,6 +3653,13 @@ var zhCN = {
3627
3653
  help: '支持的功能',
3628
3654
  placeholder: '请输入 TeX 表达式',
3629
3655
  },
3656
+ twitter: {
3657
+ embed: '嵌入 Tweet',
3658
+ remove: '删除',
3659
+ description: '在下面的输入框里,粘贴 X (Twitter) 链接。',
3660
+ url: '链接',
3661
+ urlError: '请输入有效的链接。',
3662
+ },
3630
3663
  };
3631
3664
 
3632
3665
  var ja = {
@@ -3656,7 +3689,7 @@ var ja = {
3656
3689
  formatPainter: '形式ペインタ',
3657
3690
  link: 'リンク',
3658
3691
  hr: '区切り線',
3659
- video: '動画',
3692
+ video: 'YouTube',
3660
3693
  codeBlock: 'コードブロック',
3661
3694
  heading: '見出し',
3662
3695
  heading1: '見出し 1',
@@ -3678,6 +3711,7 @@ var ja = {
3678
3711
  file: 'ファイル',
3679
3712
  emoji: '絵文字',
3680
3713
  equation: '数式',
3714
+ twitter: 'ツイート',
3681
3715
  removeColor: 'デフォルト',
3682
3716
  },
3683
3717
  slash: {
@@ -3717,10 +3751,12 @@ var ja = {
3717
3751
  hrDesc: '水平線を挿入',
3718
3752
  codeBlock: 'コードブロック',
3719
3753
  codeBlockDesc: 'コードブロックを挿入',
3720
- video: '動画',
3754
+ video: 'YouTube',
3721
3755
  videoDesc: 'YouTube から動画を挿入',
3722
3756
  equation: '数式',
3723
3757
  equationDesc: 'TeX 数式を挿入',
3758
+ twitter: 'ツイート',
3759
+ twitterDesc: 'ツイートを挿入',
3724
3760
  image: '画像',
3725
3761
  imageDesc: '画像をアップロード',
3726
3762
  file: 'ファイル',
@@ -3796,6 +3832,13 @@ var ja = {
3796
3832
  help: 'サポートされている機能',
3797
3833
  placeholder: 'TeX 数式を入力してください',
3798
3834
  },
3835
+ twitter: {
3836
+ embed: 'ツイートを埋め込む',
3837
+ remove: '削除',
3838
+ description: '下の入力欄に X (Twitter) リンクを貼り付けてください。',
3839
+ url: 'リンク',
3840
+ urlError: '有効なリンクを入力してください。',
3841
+ },
3799
3842
  };
3800
3843
 
3801
3844
  var ko = {
@@ -3825,7 +3868,7 @@ var ko = {
3825
3868
  formatPainter: '형식 복사기',
3826
3869
  link: '링크',
3827
3870
  hr: '구분선',
3828
- video: '동영상',
3871
+ video: '유튜브',
3829
3872
  codeBlock: '코드 블록',
3830
3873
  heading: '제목',
3831
3874
  heading1: '제목 1',
@@ -3847,6 +3890,7 @@ var ko = {
3847
3890
  file: '파일',
3848
3891
  emoji: '이모지',
3849
3892
  equation: '수학 공식',
3893
+ twitter: '트윗',
3850
3894
  removeColor: '기본 색상',
3851
3895
  },
3852
3896
  slash: {
@@ -3873,7 +3917,7 @@ var ko = {
3873
3917
  checklist: '체크리스트',
3874
3918
  checklistDesc: '체크리스트를 작성',
3875
3919
  table: '표',
3876
- tableDesc: '표를 삽입',
3920
+ tableDesc: '표를 추가',
3877
3921
  infoAlert: '정보 블록',
3878
3922
  infoAlertDesc: '정보 블록을 작성',
3879
3923
  tipAlert: '팁 블록',
@@ -3883,13 +3927,15 @@ var ko = {
3883
3927
  dangerAlert: '위험 블록',
3884
3928
  dangerAlertDesc: '위험 블록을 작성',
3885
3929
  hr: '구분선',
3886
- hrDesc: '구분선을 삽입',
3930
+ hrDesc: '구분선을 추가',
3887
3931
  codeBlock: '코드 블록',
3888
- codeBlockDesc: '코드 블록을 삽입',
3889
- video: '동영상',
3890
- videoDesc: '유튜브 동영상을 삽입',
3932
+ codeBlockDesc: '코드 블록을 추가',
3933
+ video: '유튜브',
3934
+ videoDesc: '유튜브 동영상을 추가',
3891
3935
  equation: '수학 공식',
3892
- equationDesc: 'TeX 수식을 삽입',
3936
+ equationDesc: 'TeX 수식을 추가',
3937
+ twitter: '트윗',
3938
+ twitterDesc: '트윗을 추가',
3893
3939
  image: '이미지',
3894
3940
  imageDesc: '이미지를 업로드',
3895
3941
  file: '파일',
@@ -3908,12 +3954,12 @@ var ko = {
3908
3954
  fitTable: '페이지 너비에 맞게 조정',
3909
3955
  cellBackground: '셀 배경 색상',
3910
3956
  column: '열',
3911
- insertColumnLeft: '왼쪽에 열을 삽입',
3912
- insertColumnRight: '오른쪽에 열을 삽입',
3957
+ insertColumnLeft: '왼쪽에 열을 추가',
3958
+ insertColumnRight: '오른쪽에 열을 추가',
3913
3959
  deleteColumn: '열을 삭제',
3914
3960
  row: '행',
3915
- insertRowAbove: '위에 행을 삽입',
3916
- insertRowBelow: '아래에 행을 삽입',
3961
+ insertRowAbove: '위에 행을 추가',
3962
+ insertRowBelow: '아래에 행을 추가',
3917
3963
  deleteRow: '행을 삭제',
3918
3964
  merge: '셀을 병합',
3919
3965
  mergeUp: '위쪽으로 셀을 병합',
@@ -3951,9 +3997,9 @@ var ko = {
3951
3997
  remove: '삭제',
3952
3998
  },
3953
3999
  video: {
3954
- embed: '동영상 삽입',
4000
+ embed: '동영상을 추가',
3955
4001
  remove: '삭제',
3956
- description: '아래 입력란에 YouTube 링크를 붙여넣으세요.',
4002
+ description: '아래 입력란에 유튜브 링크를 붙여넣으세요.',
3957
4003
  url: '링크',
3958
4004
  urlError: '유효한 링크를 입력하세요.',
3959
4005
  },
@@ -3965,6 +4011,13 @@ var ko = {
3965
4011
  help: '지원되는 기능',
3966
4012
  placeholder: 'TeX 수식을 입력하세요',
3967
4013
  },
4014
+ twitter: {
4015
+ embed: '트윗을 추가',
4016
+ remove: '삭제',
4017
+ description: '아래 입력란에 X (Twitter) 링크를 붙여넣으세요.',
4018
+ url: '링크',
4019
+ urlError: '유효한 링크를 입력하세요.',
4020
+ },
3968
4021
  };
3969
4022
 
3970
4023
  const localeTranslations = {
@@ -4439,19 +4492,28 @@ class Toolbar {
4439
4492
  this.container.append(uploadNode);
4440
4493
  fileNode.on('click', event => event.stopPropagation());
4441
4494
  fileNode.on('change', event => {
4495
+ const { requestTypes, requestMethod, requestAction, requestFieldName, requestWithCredentials, requestHeaders, transformResponse, } = editor.config[item.name];
4442
4496
  const target = event.target;
4443
4497
  const files = target.files || [];
4444
4498
  for (const file of files) {
4445
- uploadFile({
4446
- editor,
4447
- name: item.name,
4499
+ insertUploadBox({
4500
+ selection: editor.selection,
4501
+ boxName: item.name,
4448
4502
  file,
4503
+ requestTypes,
4504
+ requestMethod,
4505
+ requestAction,
4506
+ requestFieldName,
4507
+ requestWithCredentials,
4508
+ requestHeaders,
4509
+ transformResponse,
4449
4510
  onError: error => {
4450
4511
  fileNativeNode.value = '';
4451
4512
  editor.config.onMessage('error', error);
4452
4513
  },
4453
4514
  onSuccess: () => {
4454
4515
  fileNativeNode.value = '';
4516
+ editor.history.save();
4455
4517
  },
4456
4518
  });
4457
4519
  }
@@ -4687,9 +4749,7 @@ class Box {
4687
4749
  if (component === undefined) {
4688
4750
  throw new Error(`Box "${node}" has not been defined yet.`);
4689
4751
  }
4690
- const type = encode(component.type);
4691
- const name = encode(component.name);
4692
- this.node = query(template `<lake-box type="${type}" name="${name}"></lake-box>`);
4752
+ this.node = query(template `<lake-box type="${component.type}" name="${component.name}"></lake-box>`);
4693
4753
  if (component.value) {
4694
4754
  this.value = component.value;
4695
4755
  }
@@ -4906,6 +4966,281 @@ function getBox(boxNode) {
4906
4966
  return box;
4907
4967
  }
4908
4968
 
4969
+ // The CornerToolbar class represents a button group located in the top-right corner of a box.
4970
+ class CornerToolbar {
4971
+ constructor(config) {
4972
+ this.config = config;
4973
+ this.locale = this.config.locale || i18nObject('en-US');
4974
+ this.root = query(config.root);
4975
+ this.container = query('<div class="lake-corner-toolbar" />');
4976
+ }
4977
+ appendButton(item) {
4978
+ const buttonNode = query(template `
4979
+ <button type="button" name="${item.name}" tabindex="-1" />
4980
+ `);
4981
+ const tooltip = typeof item.tooltip === 'string' ? item.tooltip : item.tooltip(this.locale);
4982
+ buttonNode.attr('title', tooltip);
4983
+ if (item.icon) {
4984
+ buttonNode.append(item.icon);
4985
+ }
4986
+ this.container.append(buttonNode);
4987
+ buttonNode.on('click', event => {
4988
+ event.preventDefault();
4989
+ item.onClick(event);
4990
+ });
4991
+ }
4992
+ render() {
4993
+ const { items } = this.config;
4994
+ if (items.length === 0) {
4995
+ return;
4996
+ }
4997
+ this.root.append(this.container);
4998
+ for (const item of items) {
4999
+ this.appendButton(item);
5000
+ }
5001
+ }
5002
+ }
5003
+
5004
+ // The Resizer class represents a UI component used to resize images or videos.
5005
+ class Resizer {
5006
+ constructor(config) {
5007
+ this.config = config;
5008
+ this.root = query(config.root);
5009
+ this.target = query(config.target);
5010
+ this.container = query(template `
5011
+ <div class="lake-resizer">
5012
+ <div class="lake-resizer-top-left"></div>
5013
+ <div class="lake-resizer-top-right"></div>
5014
+ <div class="lake-resizer-bottom-left"></div>
5015
+ <div class="lake-resizer-bottom-right"></div>
5016
+ <div class="lake-resizer-info"></div>
5017
+ </div>
5018
+ `);
5019
+ }
5020
+ bindEvents(pointerNode) {
5021
+ const target = this.target;
5022
+ const infoNode = this.container.find('.lake-resizer-info');
5023
+ const isPlus = pointerNode.attr('class').indexOf('-right') >= 0;
5024
+ const initialWidth = target.width();
5025
+ const initialHeight = target.height();
5026
+ const rate = initialHeight / initialWidth;
5027
+ let clientX = 0;
5028
+ let width = 0;
5029
+ // resizing box
5030
+ const pointermoveListener = (event) => {
5031
+ const pointerEvent = event;
5032
+ const diffX = pointerEvent.clientX - clientX;
5033
+ const newWidth = Math.round(isPlus ? width + diffX : width - diffX);
5034
+ const newHeight = Math.round(rate * newWidth);
5035
+ infoNode.text(`${newWidth} x ${newHeight}`);
5036
+ target.css({
5037
+ width: `${newWidth}px`,
5038
+ height: `${newHeight}px`,
5039
+ });
5040
+ if (this.config.onResize) {
5041
+ this.config.onResize(newWidth, newHeight);
5042
+ }
5043
+ };
5044
+ // start resizing
5045
+ const pointerdownListener = (event) => {
5046
+ const pointerEvent = event;
5047
+ const pointerNativeNode = pointerNode.get(0);
5048
+ // The capture will be implicitly released after a pointerup or pointercancel event.
5049
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
5050
+ try {
5051
+ // Test case throws an exception on Firefox.
5052
+ pointerNativeNode.setPointerCapture(pointerEvent.pointerId);
5053
+ }
5054
+ catch ( /* empty */_a) { /* empty */ }
5055
+ clientX = pointerEvent.clientX;
5056
+ width = target.width();
5057
+ infoNode.text(`${initialWidth} x ${initialHeight}`);
5058
+ infoNode.show();
5059
+ pointerNode.on('pointermove', pointermoveListener);
5060
+ };
5061
+ // stop resizing
5062
+ const pointerupListner = () => {
5063
+ pointerNode.off('pointermove');
5064
+ infoNode.hide();
5065
+ width = target.width();
5066
+ const height = Math.round(rate * width);
5067
+ this.config.onStop(width, height);
5068
+ };
5069
+ // cancel resizing
5070
+ const pointercancelListner = () => {
5071
+ pointerNode.off('pointermove');
5072
+ infoNode.hide();
5073
+ };
5074
+ pointerNode.on('pointerdown', pointerdownListener);
5075
+ pointerNode.on('pointerup', pointerupListner);
5076
+ pointerNode.on('pointercancel', pointercancelListner);
5077
+ }
5078
+ render() {
5079
+ this.bindEvents(this.container.find('.lake-resizer-top-left'));
5080
+ this.bindEvents(this.container.find('.lake-resizer-top-right'));
5081
+ this.bindEvents(this.container.find('.lake-resizer-bottom-left'));
5082
+ this.bindEvents(this.container.find('.lake-resizer-bottom-right'));
5083
+ this.root.append(this.container);
5084
+ }
5085
+ }
5086
+
5087
+ /**
5088
+ * Returns the localized string.
5089
+ */
5090
+ function getLocaleString(locale, value) {
5091
+ return typeof value === 'string' ? value : value(locale);
5092
+ }
5093
+ /**
5094
+ * Appends a corner toolbar to the iframe box.
5095
+ */
5096
+ function appendCornerToolbar(config, box) {
5097
+ const editor = box.getEditor();
5098
+ const boxContainer = box.getContainer();
5099
+ const rootNode = boxContainer.find('.lake-iframe');
5100
+ if (rootNode.find('.lake-corner-toolbar').length > 0) {
5101
+ return;
5102
+ }
5103
+ new CornerToolbar({
5104
+ locale: editor.locale,
5105
+ root: rootNode,
5106
+ items: [
5107
+ {
5108
+ name: 'remove',
5109
+ icon: icons.get('remove'),
5110
+ tooltip: config.deleteButtonText,
5111
+ onClick: event => {
5112
+ event.stopPropagation();
5113
+ editor.selection.removeBox(box);
5114
+ editor.history.save();
5115
+ },
5116
+ },
5117
+ ],
5118
+ }).render();
5119
+ }
5120
+ /**
5121
+ * Shows the iframe in the box.
5122
+ */
5123
+ function showIframe(config, box) {
5124
+ const editor = box.getEditor();
5125
+ const boxContainer = box.getContainer();
5126
+ const value = box.value;
5127
+ const width = value.width || config.width;
5128
+ const height = value.height || config.height;
5129
+ const iframeNode = query('<iframe></iframe>');
5130
+ iframeNode.css({
5131
+ width,
5132
+ height,
5133
+ });
5134
+ const iframeAttributes = config.iframeAttributes(value.url);
5135
+ for (const key of Object.keys(iframeAttributes)) {
5136
+ iframeNode.attr(key, iframeAttributes[key]);
5137
+ }
5138
+ const placeholderNode = query('<div class="lake-iframe-placeholder" />');
5139
+ placeholderNode.css({
5140
+ width,
5141
+ height,
5142
+ });
5143
+ if (config.iframePlaceholder) {
5144
+ placeholderNode.append(config.iframePlaceholder);
5145
+ }
5146
+ const rootNode = boxContainer.find('.lake-iframe');
5147
+ iframeNode.on('load', () => {
5148
+ placeholderNode.remove();
5149
+ if (editor.readonly) {
5150
+ return;
5151
+ }
5152
+ if (config.resize === true && rootNode.find('.lake-resizer').length === 0) {
5153
+ new Resizer({
5154
+ root: rootNode,
5155
+ target: iframeNode,
5156
+ onStop: (newWidth, newHeight) => {
5157
+ box.updateValue({
5158
+ width: `${newWidth}px`,
5159
+ height: `${newHeight}px`,
5160
+ });
5161
+ editor.selection.selectBox(box);
5162
+ editor.history.save();
5163
+ },
5164
+ }).render();
5165
+ }
5166
+ });
5167
+ if (config.validUrl(value.url)) {
5168
+ rootNode.prepend(iframeNode);
5169
+ }
5170
+ else {
5171
+ placeholderNode.css('position', 'static');
5172
+ }
5173
+ rootNode.prepend(placeholderNode);
5174
+ if (config.beforeIframeLoad) {
5175
+ config.beforeIframeLoad(box);
5176
+ }
5177
+ }
5178
+ /**
5179
+ * Creates an iframe box component with configurable properties.
5180
+ * This component supports rendering an iframe with customizable attributes, resizing, and toolbar functionalities.
5181
+ */
5182
+ function createIframeBox(config) {
5183
+ return {
5184
+ type: config.type,
5185
+ name: config.name,
5186
+ render: box => {
5187
+ const editor = box.getEditor();
5188
+ const locale = editor.locale;
5189
+ const value = box.value;
5190
+ const boxContainer = box.getContainer();
5191
+ const rootNode = query('<div class="lake-iframe" />');
5192
+ boxContainer.empty();
5193
+ boxContainer.append(rootNode);
5194
+ if (value.url === undefined) {
5195
+ if (editor.readonly) {
5196
+ box.node.hide();
5197
+ return;
5198
+ }
5199
+ const formNode = query(template `
5200
+ <div class="lake-iframe-form">
5201
+ <div class="lake-description">${getLocaleString(locale, config.formDescription)}</div>
5202
+ <div class="lake-input-label">${getLocaleString(locale, config.urlLabel || '')}</div>
5203
+ <div class="lake-input-field">
5204
+ <input type="text" name="url" placeholder="${config.urlPlaceholder}" />
5205
+ </div>
5206
+ </div>
5207
+ `);
5208
+ const button = new Button({
5209
+ root: formNode.find('.lake-input-field'),
5210
+ name: 'embed',
5211
+ type: 'primary',
5212
+ text: getLocaleString(locale, config.embedButtonText),
5213
+ onClick: () => {
5214
+ const url = formNode.find('input[name="url"]').value();
5215
+ if (!config.validUrl(url)) {
5216
+ editor.config.onMessage('error', getLocaleString(locale, config.urlError));
5217
+ return;
5218
+ }
5219
+ box.updateValue('url', url);
5220
+ editor.history.save();
5221
+ formNode.remove();
5222
+ showIframe(config, box);
5223
+ },
5224
+ });
5225
+ formNode.find('input[name="url"]').on('keydown', event => {
5226
+ if (isKeyHotkey('enter', event)) {
5227
+ event.preventDefault();
5228
+ button.node.emit('click');
5229
+ }
5230
+ });
5231
+ button.render();
5232
+ rootNode.append(formNode);
5233
+ }
5234
+ else {
5235
+ showIframe(config, box);
5236
+ }
5237
+ if (!editor.readonly) {
5238
+ appendCornerToolbar(config, box);
5239
+ }
5240
+ },
5241
+ };
5242
+ }
5243
+
4909
5244
  /**
4910
5245
  * The Fragment interface represents a lightweight document object that has no parent.
4911
5246
  * It is designed for efficient manipulation of document structures without affecting the main DOM.
@@ -6273,7 +6608,7 @@ function removeBox(range) {
6273
6608
  return box;
6274
6609
  }
6275
6610
 
6276
- var version = "0.3.9";
6611
+ var version = "0.3.11";
6277
6612
 
6278
6613
  // Converts the custom HTML tags to the special tags that can not be parsed by browser.
6279
6614
  function denormalizeValue(value) {
@@ -8490,7 +8825,6 @@ var paste = (editor) => {
8490
8825
  return;
8491
8826
  }
8492
8827
  editor.event.on('paste', event => {
8493
- const { requestTypes } = editor.config.image;
8494
8828
  const range = editor.selection.range;
8495
8829
  if (range.isInsideBox) {
8496
8830
  return;
@@ -8504,12 +8838,22 @@ var paste = (editor) => {
8504
8838
  // upload file
8505
8839
  if (dataTransfer.files.length > 0) {
8506
8840
  for (const file of dataTransfer.files) {
8841
+ const pluginName = file.type.indexOf('image/') === 0 ? 'image' : 'file';
8842
+ const { requestTypes, requestMethod, requestAction, requestFieldName, requestWithCredentials, requestHeaders, transformResponse, } = editor.config[pluginName];
8507
8843
  if (requestTypes.indexOf(file.type) >= 0) {
8508
- uploadFile({
8509
- editor,
8510
- name: file.type.indexOf('image/') === 0 ? 'image' : 'file',
8844
+ insertUploadBox({
8845
+ selection: editor.selection,
8846
+ boxName: pluginName,
8511
8847
  file,
8848
+ requestTypes,
8849
+ requestMethod,
8850
+ requestAction,
8851
+ requestFieldName,
8852
+ requestWithCredentials,
8853
+ requestHeaders,
8854
+ transformResponse,
8512
8855
  onError: error => editor.config.onMessage('error', error),
8856
+ onSuccess: () => editor.history.save(),
8513
8857
  });
8514
8858
  }
8515
8859
  }
@@ -10682,124 +11026,6 @@ var codeBlock = (editor) => {
10682
11026
  });
10683
11027
  };
10684
11028
 
10685
- // The CornerToolbar class represents a button group located in the top-right corner of a box.
10686
- class CornerToolbar {
10687
- constructor(config) {
10688
- this.config = config;
10689
- this.locale = this.config.locale || i18nObject('en-US');
10690
- this.root = query(config.root);
10691
- this.container = query('<div class="lake-corner-toolbar" />');
10692
- }
10693
- appendButton(item) {
10694
- const buttonNode = query(template `
10695
- <button type="button" name="${item.name}" tabindex="-1" />
10696
- `);
10697
- const tooltip = typeof item.tooltip === 'string' ? item.tooltip : item.tooltip(this.locale);
10698
- buttonNode.attr('title', tooltip);
10699
- if (item.icon) {
10700
- buttonNode.append(item.icon);
10701
- }
10702
- this.container.append(buttonNode);
10703
- buttonNode.on('click', event => {
10704
- event.preventDefault();
10705
- item.onClick(event);
10706
- });
10707
- }
10708
- render() {
10709
- const { items } = this.config;
10710
- if (items.length === 0) {
10711
- return;
10712
- }
10713
- this.root.append(this.container);
10714
- for (const item of items) {
10715
- this.appendButton(item);
10716
- }
10717
- }
10718
- }
10719
-
10720
- // The Resizer class represents a UI component used to resize images or videos.
10721
- class Resizer {
10722
- constructor(config) {
10723
- this.config = config;
10724
- this.root = query(config.root);
10725
- this.target = query(config.target);
10726
- this.container = query(template `
10727
- <div class="lake-resizer">
10728
- <div class="lake-resizer-top-left"></div>
10729
- <div class="lake-resizer-top-right"></div>
10730
- <div class="lake-resizer-bottom-left"></div>
10731
- <div class="lake-resizer-bottom-right"></div>
10732
- <div class="lake-resizer-info"></div>
10733
- </div>
10734
- `);
10735
- }
10736
- bindEvents(pointerNode) {
10737
- const target = this.target;
10738
- const infoNode = this.container.find('.lake-resizer-info');
10739
- const isPlus = pointerNode.attr('class').indexOf('-right') >= 0;
10740
- const initialWidth = target.width();
10741
- const initialHeight = target.height();
10742
- const rate = initialHeight / initialWidth;
10743
- let clientX = 0;
10744
- let width = 0;
10745
- // resizing box
10746
- const pointermoveListener = (event) => {
10747
- const pointerEvent = event;
10748
- const diffX = pointerEvent.clientX - clientX;
10749
- const newWidth = Math.round(isPlus ? width + diffX : width - diffX);
10750
- const newHeight = Math.round(rate * newWidth);
10751
- infoNode.text(`${newWidth} x ${newHeight}`);
10752
- target.css({
10753
- width: `${newWidth}px`,
10754
- height: `${newHeight}px`,
10755
- });
10756
- if (this.config.onResize) {
10757
- this.config.onResize(newWidth, newHeight);
10758
- }
10759
- };
10760
- // start resizing
10761
- const pointerdownListener = (event) => {
10762
- const pointerEvent = event;
10763
- const pointerNativeNode = pointerNode.get(0);
10764
- // The capture will be implicitly released after a pointerup or pointercancel event.
10765
- // https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
10766
- try {
10767
- // Test case throws an exception on Firefox.
10768
- pointerNativeNode.setPointerCapture(pointerEvent.pointerId);
10769
- }
10770
- catch ( /* empty */_a) { /* empty */ }
10771
- clientX = pointerEvent.clientX;
10772
- width = target.width();
10773
- infoNode.text(`${initialWidth} x ${initialHeight}`);
10774
- infoNode.show();
10775
- pointerNode.on('pointermove', pointermoveListener);
10776
- };
10777
- // stop resizing
10778
- const pointerupListner = () => {
10779
- pointerNode.off('pointermove');
10780
- infoNode.hide();
10781
- width = target.width();
10782
- const height = Math.round(rate * width);
10783
- this.config.onStop(width, height);
10784
- };
10785
- // cancel resizing
10786
- const pointercancelListner = () => {
10787
- pointerNode.off('pointermove');
10788
- infoNode.hide();
10789
- };
10790
- pointerNode.on('pointerdown', pointerdownListener);
10791
- pointerNode.on('pointerup', pointerupListner);
10792
- pointerNode.on('pointercancel', pointercancelListner);
10793
- }
10794
- render() {
10795
- this.bindEvents(this.container.find('.lake-resizer-top-left'));
10796
- this.bindEvents(this.container.find('.lake-resizer-top-right'));
10797
- this.bindEvents(this.container.find('.lake-resizer-bottom-left'));
10798
- this.bindEvents(this.container.find('.lake-resizer-bottom-right'));
10799
- this.root.append(this.container);
10800
- }
10801
- }
10802
-
10803
11029
  const alignValueMap = {
10804
11030
  start: 'left',
10805
11031
  end: 'right',
@@ -10832,8 +11058,8 @@ function renderFloatingToolbar(box) {
10832
11058
  onClick: () => {
10833
11059
  const boxContainer = box.getContainer();
10834
11060
  const captionNode = boxContainer.find('.lake-image-caption');
10835
- const caption = captionNode.text().trim();
10836
- if (caption === '') {
11061
+ const caption = captionNode.text();
11062
+ if (caption.trim() === '') {
10837
11063
  captionNode.addClass('lake-placeholder');
10838
11064
  }
10839
11065
  showCaption(box, captionNode);
@@ -11066,11 +11292,11 @@ function openFullScreen(box) {
11066
11292
  function renderCaption(box) {
11067
11293
  const editor = box.getEditor();
11068
11294
  const boxContainer = box.getContainer();
11069
- const defaultCaption = (box.value.caption || '').trim();
11295
+ const defaultCaption = box.value.caption || '';
11070
11296
  const captionNode = query('<div class="lake-image-caption" />');
11071
11297
  captionNode.text(defaultCaption);
11072
11298
  boxContainer.append(captionNode);
11073
- if (defaultCaption === '') {
11299
+ if (defaultCaption.trim() === '') {
11074
11300
  hideCaption(box, captionNode);
11075
11301
  }
11076
11302
  else {
@@ -11093,7 +11319,7 @@ function renderCaption(box) {
11093
11319
  immediate: false,
11094
11320
  });
11095
11321
  captionNode.on('input', () => {
11096
- const caption = captionNode.text().trim();
11322
+ const caption = captionNode.text();
11097
11323
  if (caption === '') {
11098
11324
  captionNode.addClass('lake-placeholder');
11099
11325
  }
@@ -11121,8 +11347,8 @@ function renderCaption(box) {
11121
11347
  }
11122
11348
  });
11123
11349
  captionNode.on('focusout', () => {
11124
- const caption = captionNode.text().trim();
11125
- if (caption === '') {
11350
+ const caption = captionNode.text();
11351
+ if (caption.trim() === '') {
11126
11352
  hideCaption(box, captionNode);
11127
11353
  }
11128
11354
  });
@@ -11428,145 +11654,36 @@ var image = (editor) => {
11428
11654
  });
11429
11655
  };
11430
11656
 
11431
- function getVideoId(url) {
11432
- const result = /\w+$/.exec(url || '');
11657
+ /**
11658
+ * Extracts ID from the specified URL.
11659
+ */
11660
+ function getId$1(url) {
11661
+ const result = /[\w\-]+$/.exec(url || '');
11433
11662
  return result ? result[0] : '';
11434
11663
  }
11435
- function appendButtonGroup(box) {
11436
- const editor = box.getEditor();
11437
- const boxContainer = box.getContainer();
11438
- const rootNode = boxContainer.find('.lake-video');
11439
- if (rootNode.find('.lake-corner-toolbar').length > 0) {
11440
- return;
11441
- }
11442
- new CornerToolbar({
11443
- locale: editor.locale,
11444
- root: rootNode,
11445
- items: [
11446
- {
11447
- name: 'remove',
11448
- icon: icons.get('remove'),
11449
- tooltip: editor.locale.video.remove(),
11450
- onClick: event => {
11451
- event.stopPropagation();
11452
- editor.selection.removeBox(box);
11453
- editor.history.save();
11454
- },
11455
- },
11456
- ],
11457
- }).render();
11458
- }
11459
- function showVideo(box) {
11460
- const editor = box.getEditor();
11461
- const boxContainer = box.getContainer();
11462
- const value = box.value;
11463
- const width = value.width || 560;
11464
- const height = value.height || 315;
11465
- boxContainer.css({
11466
- width: `${width}px`,
11467
- height: `${height}px`,
11468
- });
11469
- const videoId = getVideoId(value.url);
11470
- if (videoId === '') {
11471
- throw new Error(`Invalid link: ${value.url}`);
11472
- }
11473
- // YouTube URL: https://www.youtube.com/watch?v=5sMBhDv4sik
11474
- // The script for embedding YouTube:
11475
- // <iframe width="560" height="315" src="https://www.youtube.com/embed/5sMBhDv4sik" title="YouTube video player"
11476
- // frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
11477
- // referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
11478
- const iframeNode = query(template `
11479
- <iframe width="100%" height="${height}" src="https://www.youtube.com/embed/${videoId}" title="YouTube video player"
11480
- frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
11481
- referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
11482
- `);
11483
- const rootNode = boxContainer.find('.lake-video');
11484
- if (!editor.readonly) {
11485
- iframeNode.on('load', () => {
11486
- appendButtonGroup(box);
11487
- new Resizer({
11488
- root: rootNode,
11489
- target: boxContainer,
11490
- onResize: (newWidth, newHeight) => {
11491
- iframeNode.attr({
11492
- height: newHeight.toString(),
11493
- });
11494
- },
11495
- onStop: (newWidth, newHeight) => {
11496
- box.updateValue({
11497
- width: newWidth,
11498
- height: newHeight,
11499
- });
11500
- editor.history.save();
11501
- },
11502
- }).render();
11503
- });
11504
- }
11505
- rootNode.prepend(iframeNode);
11506
- }
11507
- var videoBox = {
11664
+ const videoBox = createIframeBox({
11508
11665
  type: 'inline',
11509
11666
  name: 'video',
11510
- render: box => {
11511
- const editor = box.getEditor();
11512
- const locale = editor.locale;
11513
- const value = box.value;
11514
- const boxContainer = box.getContainer();
11515
- const rootNode = query('<div class="lake-video" />');
11516
- boxContainer.empty();
11517
- boxContainer.css({
11518
- width: '',
11519
- height: '',
11520
- });
11521
- boxContainer.append(rootNode);
11522
- if (!value.url) {
11523
- if (editor.readonly) {
11524
- box.node.hide();
11525
- return;
11526
- }
11527
- const formNode = query(template `
11528
- <div class="lake-video-form">
11529
- <div class="lake-row lake-desc-row">${locale.video.description()}</div>
11530
- <div class="lake-row">${locale.video.url()}</div>
11531
- <div class="lake-row">
11532
- <input type="text" name="url" placeholder="https://www.youtube.com/watch?v=..." />
11533
- </div>
11534
- <div class="lake-row lake-button-row"></div>
11535
- </div>
11536
- `);
11537
- const button = new Button({
11538
- root: formNode.find('.lake-button-row'),
11539
- name: 'embed',
11540
- type: 'primary',
11541
- text: locale.video.embed(),
11542
- onClick: () => {
11543
- const url = formNode.find('input[name="url"]').value();
11544
- if (url.indexOf('https://www.youtube.com/') < 0 || getVideoId(url) === '') {
11545
- editor.config.onMessage('error', locale.video.urlError());
11546
- return;
11547
- }
11548
- box.updateValue('url', url);
11549
- editor.history.save();
11550
- formNode.remove();
11551
- showVideo(box);
11552
- },
11553
- });
11554
- formNode.find('input[name="url"]').on('keydown', event => {
11555
- if (isKeyHotkey('enter', event)) {
11556
- event.preventDefault();
11557
- button.node.emit('click');
11558
- }
11559
- });
11560
- button.render();
11561
- rootNode.append(formNode);
11562
- appendButtonGroup(box);
11563
- }
11564
- else {
11565
- showVideo(box);
11566
- }
11567
- },
11568
- };
11569
-
11667
+ width: '560px',
11668
+ height: '315px',
11669
+ formDescription: locale => locale.video.description(),
11670
+ urlLabel: locale => locale.video.url(),
11671
+ urlPlaceholder: 'https://www.youtube.com/watch?v=...',
11672
+ embedButtonText: locale => locale.video.embed(),
11673
+ deleteButtonText: locale => locale.video.remove(),
11674
+ validUrl: url => url.indexOf('https://www.youtube.com/') === 0 && getId$1(url) !== '',
11675
+ urlError: locale => locale.video.urlError(),
11676
+ iframePlaceholder: icons.get('video'),
11677
+ iframeAttributes: url => ({
11678
+ src: `https://www.youtube.com/embed/${getId$1(url)}`,
11679
+ title: 'YouTube video player',
11680
+ frameborder: '0',
11681
+ allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share',
11682
+ referrerpolicy: 'strict-origin-when-cross-origin',
11683
+ allowfullscreen: 'true',
11684
+ }),
11685
+ resize: true,
11686
+ });
11570
11687
  var video = (editor) => {
11571
11688
  if (editor.readonly) {
11572
11689
  return;
@@ -11576,7 +11693,10 @@ var video = (editor) => {
11576
11693
  const box = editor.selection.insertBox('video', value);
11577
11694
  editor.history.save();
11578
11695
  if (box) {
11579
- box.getContainer().find('input[name="url"]').focus();
11696
+ const urlInput = box.getContainer().find('input[name="url"]');
11697
+ if (urlInput.length > 0) {
11698
+ urlInput.focus();
11699
+ }
11580
11700
  }
11581
11701
  },
11582
11702
  });
@@ -12193,7 +12313,6 @@ var mention = (editor) => {
12193
12313
  if (editor.readonly) {
12194
12314
  return;
12195
12315
  }
12196
- const { requestAction, requestMethod, items } = editor.config.mention;
12197
12316
  let menu = null;
12198
12317
  const selectListener = (event, item) => {
12199
12318
  if (menu) {
@@ -12227,9 +12346,14 @@ var mention = (editor) => {
12227
12346
  return;
12228
12347
  }
12229
12348
  if (!menu) {
12349
+ const { requestAction, items } = editor.config.mention;
12230
12350
  if (requestAction) {
12351
+ const { requestMethod, requestWithCredentials, requestHeaders, transformResponse } = editor.config.mention;
12231
12352
  request({
12232
12353
  onSuccess: body => {
12354
+ if (transformResponse) {
12355
+ body = transformResponse(body);
12356
+ }
12233
12357
  if (!body.data) {
12234
12358
  return;
12235
12359
  }
@@ -12243,6 +12367,8 @@ var mention = (editor) => {
12243
12367
  },
12244
12368
  action: requestAction,
12245
12369
  method: requestMethod,
12370
+ withCredentials: requestWithCredentials,
12371
+ headers: requestHeaders,
12246
12372
  });
12247
12373
  }
12248
12374
  else {
@@ -12297,6 +12423,81 @@ var mention = (editor) => {
12297
12423
  };
12298
12424
  };
12299
12425
 
12426
+ /**
12427
+ * Extracts ID from the specified URL.
12428
+ */
12429
+ function getId(url) {
12430
+ const result = /\d+$/.exec(url || '');
12431
+ return result ? result[0] : '';
12432
+ }
12433
+ /**
12434
+ * Returns the current theme.
12435
+ */
12436
+ function getTheme() {
12437
+ return document.documentElement.classList.contains('lake-dark') ? 'dark' : 'light';
12438
+ }
12439
+ const twitterBox = createIframeBox({
12440
+ type: 'inline',
12441
+ name: 'twitter',
12442
+ width: '550px',
12443
+ height: '300px',
12444
+ formDescription: locale => locale.twitter.description(),
12445
+ urlLabel: locale => locale.twitter.url(),
12446
+ urlPlaceholder: 'https://x.com/username/status/...',
12447
+ embedButtonText: locale => locale.twitter.embed(),
12448
+ deleteButtonText: locale => locale.twitter.remove(),
12449
+ validUrl: url => (url.indexOf('https://x.com/') === 0 || url.indexOf('https://twitter.com/') === 0) && getId(url) !== '',
12450
+ urlError: locale => locale.twitter.urlError(),
12451
+ iframePlaceholder: icons.get('twitter'),
12452
+ iframeAttributes: url => {
12453
+ return {
12454
+ src: `https://platform.twitter.com/embed/Tweet.html?id=${getId(url)}&theme=${getTheme()}`,
12455
+ title: 'Twitter tweet',
12456
+ scrolling: 'no',
12457
+ frameborder: '0',
12458
+ allowtransparency: 'true',
12459
+ allowfullscreen: 'true',
12460
+ };
12461
+ },
12462
+ beforeIframeLoad: box => {
12463
+ const boxContainer = box.getContainer();
12464
+ const placeholder = boxContainer.find('.lake-iframe-placeholder');
12465
+ const iframe = boxContainer.find('iframe');
12466
+ if (getTheme() === 'dark') {
12467
+ iframe.css('border-radius', '13px');
12468
+ }
12469
+ const messageListener = (event) => {
12470
+ if (event.origin === 'https://platform.twitter.com') {
12471
+ const params = event.data['twttr.embed'].params;
12472
+ const height = params[0].height;
12473
+ if (height > 0) {
12474
+ placeholder.css('height', `${height}px`);
12475
+ iframe.css('height', `${height}px`);
12476
+ window.removeEventListener('message', messageListener);
12477
+ }
12478
+ }
12479
+ };
12480
+ window.addEventListener('message', messageListener);
12481
+ },
12482
+ });
12483
+ var twitter = (editor) => {
12484
+ if (editor.readonly) {
12485
+ return;
12486
+ }
12487
+ editor.command.add('twitter', {
12488
+ execute: (value) => {
12489
+ const box = editor.selection.insertBox('twitter', value);
12490
+ editor.history.save();
12491
+ if (box) {
12492
+ const urlInput = box.getContainer().find('input[name="url"]');
12493
+ if (urlInput.length > 0) {
12494
+ urlInput.focus();
12495
+ }
12496
+ }
12497
+ },
12498
+ });
12499
+ };
12500
+
12300
12501
  const headingTypeMap = new Map([
12301
12502
  ['#', 'h1'],
12302
12503
  ['##', 'h2'],
@@ -13489,6 +13690,16 @@ const slashItems = [
13489
13690
  editor.command.execute(value);
13490
13691
  },
13491
13692
  },
13693
+ {
13694
+ name: 'twitter',
13695
+ type: 'button',
13696
+ icon: icons.get('twitter'),
13697
+ title: locale => locale.slash.twitter(),
13698
+ description: locale => locale.slash.twitterDesc(),
13699
+ onClick: (editor, value) => {
13700
+ editor.command.execute(value);
13701
+ },
13702
+ },
13492
13703
  {
13493
13704
  name: 'image',
13494
13705
  type: 'upload',
@@ -13643,20 +13854,29 @@ var slash = (editor) => {
13643
13854
  if (!fileNode) {
13644
13855
  return;
13645
13856
  }
13857
+ const { requestTypes, requestMethod, requestAction, requestFieldName, requestWithCredentials, requestHeaders, transformResponse, } = editor.config[item.name];
13646
13858
  const target = event.target;
13647
13859
  const fileNativeNode = fileNode.get(0);
13648
13860
  const files = target.files || [];
13649
13861
  for (const file of files) {
13650
- uploadFile({
13651
- editor,
13652
- name: item.name,
13862
+ insertUploadBox({
13863
+ selection: editor.selection,
13864
+ boxName: item.name,
13653
13865
  file,
13866
+ requestTypes,
13867
+ requestMethod,
13868
+ requestAction,
13869
+ requestFieldName,
13870
+ requestWithCredentials,
13871
+ requestHeaders,
13872
+ transformResponse,
13654
13873
  onError: error => {
13655
13874
  fileNativeNode.value = '';
13656
13875
  editor.config.onMessage('error', error);
13657
13876
  },
13658
13877
  onSuccess: () => {
13659
13878
  fileNativeNode.value = '';
13879
+ editor.history.save();
13660
13880
  },
13661
13881
  });
13662
13882
  }
@@ -13736,6 +13956,7 @@ Editor.box.add(imageBox);
13736
13956
  Editor.box.add(videoBox);
13737
13957
  Editor.box.add(fileBox);
13738
13958
  Editor.box.add(mentionBox);
13959
+ Editor.box.add(twitterBox);
13739
13960
  Editor.plugin.add('copy', copy);
13740
13961
  Editor.plugin.add('cut', cut);
13741
13962
  Editor.plugin.add('paste', paste);
@@ -13772,6 +13993,7 @@ Editor.plugin.add('emoji', emoji);
13772
13993
  Editor.plugin.add('equation', equation);
13773
13994
  Editor.plugin.add('specialCharacter', specialCharacter);
13774
13995
  Editor.plugin.add('mention', mention);
13996
+ Editor.plugin.add('twitter', twitter);
13775
13997
  Editor.plugin.add('markdown', markdown);
13776
13998
  Editor.plugin.add('enterKey', enterKey);
13777
13999
  Editor.plugin.add('shiftEnterKey', shiftEnterKey);
@@ -13782,5 +14004,5 @@ Editor.plugin.add('arrowKeys', arrowKeys);
13782
14004
  Editor.plugin.add('escapeKey', escapeKey);
13783
14005
  Editor.plugin.add('slash', slash);
13784
14006
 
13785
- export { Box, Button, Dropdown, Editor, Fragment, HTMLParser, Nodes, Range, TextParser, Toolbar, addMark, deleteContents, getBox, getContentRules, icons, insertBlock, insertBookmark, insertBox, insertContents, query, removeBox, removeMark, setBlocks, splitBlock$1 as splitBlock, splitMarks, template, toBookmark, toHex };
14007
+ export { Box, Button, Dropdown, Editor, Fragment, HTMLParser, Nodes, Range, TextParser, Toolbar, addMark, createIframeBox, deleteContents, getBox, getContentRules, icons, insertBlock, insertBookmark, insertBox, insertContents, query, removeBox, removeMark, setBlocks, splitBlock$1 as splitBlock, splitMarks, template, toBookmark, toHex };
13786
14008
  //# sourceMappingURL=lake.js.map