jodit 4.12.18 → 4.12.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +6 -6
  4. package/es2015/jodit.js +195 -44
  5. package/es2015/jodit.min.js +6 -6
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +1 -1
  8. package/es2015/plugins/debug/debug.min.js +1 -1
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  12. package/es2018/jodit.fat.min.js +6 -6
  13. package/es2018/jodit.min.js +6 -6
  14. package/es2018/plugins/debug/debug.min.js +1 -1
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +8 -8
  18. package/es2021/jodit.js +194 -44
  19. package/es2021/jodit.min.js +8 -8
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +1 -1
  22. package/es2021/plugins/debug/debug.min.js +1 -1
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +8 -8
  28. package/es2021.en/jodit.js +194 -44
  29. package/es2021.en/jodit.min.js +8 -8
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +1 -1
  32. package/es2021.en/plugins/debug/debug.min.js +1 -1
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +214 -50
  39. package/es5/jodit.min.css +2 -2
  40. package/es5/jodit.min.js +2 -2
  41. package/es5/plugins/debug/debug.css +1 -1
  42. package/es5/plugins/debug/debug.js +1 -1
  43. package/es5/plugins/debug/debug.min.js +1 -1
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  47. package/es5/polyfills.fat.min.js +1 -1
  48. package/es5/polyfills.js +1 -1
  49. package/es5/polyfills.min.js +1 -1
  50. package/esm/core/constants.js +1 -1
  51. package/esm/core/helpers/html/apply-styles.js +11 -0
  52. package/esm/core/helpers/html/clean-from-word.js +9 -0
  53. package/esm/core/helpers/html/safe-html.js +71 -19
  54. package/esm/core/helpers/html/strip-tags.d.ts +1 -1
  55. package/esm/core/helpers/html/strip-tags.js +7 -3
  56. package/esm/core/helpers/utils/convert-media-url-to-video-embed.js +41 -19
  57. package/esm/jodit.js +20 -0
  58. package/esm/modules/uploader/config.js +11 -1
  59. package/esm/plugins/color/config.js +12 -3
  60. package/esm/plugins/hotkeys/config.js +1 -1
  61. package/esm/plugins/indent/config.js +20 -6
  62. package/esm/plugins/paste/paste.js +6 -1
  63. package/esm/plugins/paste-from-word/paste-from-word.js +1 -1
  64. package/package.json +1 -1
  65. package/types/core/helpers/html/strip-tags.d.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.18
4
+ * Version: v4.12.20
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1802,7 +1802,7 @@ __webpack_require__.r(__webpack_exports__);
1802
1802
  * ```
1803
1803
  * @packageDocumentation
1804
1804
  * @module constants
1805
- */ const APP_VERSION = "4.12.18";
1805
+ */ const APP_VERSION = "4.12.20";
1806
1806
  // prettier-ignore
1807
1807
  const ES = "es2021";
1808
1808
  const IS_ES_MODERN = true;
@@ -5767,6 +5767,16 @@ function normalizeCSS(s) {
5767
5767
  iframeDoc.open();
5768
5768
  iframeDoc.write(html);
5769
5769
  iframeDoc.close();
5770
+ // Word marks its auto-generated list markers (the literal
5771
+ // bullet/number, e.g. `1.` or `·`) with `mso-list:Ignore`.
5772
+ // They are display-only and must not be imported, otherwise
5773
+ // the marker text leaks into the content. Drop them before any
5774
+ // style normalization strips the `mso-list` hint. See #948
5775
+ jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_1__.Dom.each(iframeDoc.body, (node)=>{
5776
+ if (jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_1__.Dom.isElement(node) && /mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
5777
+ jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_1__.Dom.safeRemove(node);
5778
+ }
5779
+ });
5770
5780
  try {
5771
5781
  for(let i = 0; i < iframeDoc.styleSheets.length; i += 1){
5772
5782
  const rules = iframeDoc.styleSheets[i].cssRules;
@@ -5864,6 +5874,15 @@ function normalizeCSS(s) {
5864
5874
  jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_1__.Dom.unwrap(node);
5865
5875
  break;
5866
5876
  default:
5877
+ // Word marks its auto-generated list markers
5878
+ // (the literal bullet/number, e.g. `1.` or `·`)
5879
+ // with `mso-list:Ignore`. They are display-only
5880
+ // and must not be imported, otherwise the marker
5881
+ // text leaks into the content. See #948
5882
+ if (/mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
5883
+ marks.push(node);
5884
+ break;
5885
+ }
5867
5886
  (0,jodit_core_helpers_array_to_array__WEBPACK_IMPORTED_MODULE_2__.toArray)(node.attributes).forEach((attr)=>{
5868
5887
  if ([
5869
5888
  'src',
@@ -6004,20 +6023,20 @@ function normalizeCSS(s) {
6004
6023
  return;
6005
6024
  }
6006
6025
  const removeEvents = options.removeEventAttributes ?? options.removeOnError;
6007
- if (removeEvents) {
6008
- removeAllEventAttributes(box);
6009
- (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.$$)('*', box).forEach((elm)=>removeAllEventAttributes(elm));
6010
- } else if (options.removeOnError) {
6011
- sanitizeHTMLElement(box, options);
6012
- (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.$$)('[onerror]', box).forEach((elm)=>sanitizeHTMLElement(elm, options));
6013
- }
6014
- if (options.safeJavaScriptLink) {
6015
- sanitizeHTMLElement(box, options);
6016
- (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.$$)('a[href^="javascript"]', box).forEach((elm)=>sanitizeHTMLElement(elm, options));
6017
- }
6018
- if (options.safeLinksTarget) {
6019
- (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.$$)('a[target="_blank"]', box).forEach((elm)=>{
6020
- const rel = elm.getAttribute('rel') || '';
6026
+ // Single synchronous traversal of the subtree. Besides removing event
6027
+ // handlers and `javascript:` links, `sanitizeHTMLElement` neutralises
6028
+ // executable `iframe[srcdoc]`, `data:text/html` / SVG `data:` document
6029
+ // sources and dangerous schemes in every URL-bearing attribute.
6030
+ const process = (node)=>{
6031
+ if (!jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isElement(node)) {
6032
+ return;
6033
+ }
6034
+ if (removeEvents) {
6035
+ removeAllEventAttributes(node);
6036
+ }
6037
+ sanitizeHTMLElement(node, options);
6038
+ if (options.safeLinksTarget && node.nodeName === 'A' && node.getAttribute('target') === '_blank') {
6039
+ const rel = node.getAttribute('rel') || '';
6021
6040
  const parts = rel.split(/\s+/).filter(Boolean);
6022
6041
  if (!parts.includes('noopener')) {
6023
6042
  parts.push('noopener');
@@ -6025,9 +6044,11 @@ function normalizeCSS(s) {
6025
6044
  if (!parts.includes('noreferrer')) {
6026
6045
  parts.push('noreferrer');
6027
6046
  }
6028
- (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.attr)(elm, 'rel', parts.join(' '));
6029
- });
6030
- }
6047
+ (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.attr)(node, 'rel', parts.join(' '));
6048
+ }
6049
+ };
6050
+ process(box);
6051
+ jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.each(box, process);
6031
6052
  }
6032
6053
  /**
6033
6054
  * Remove all on* event handler attributes from an element
@@ -6048,6 +6069,41 @@ function normalizeCSS(s) {
6048
6069
  }
6049
6070
  return effected;
6050
6071
  }
6072
+ /**
6073
+ * URL-bearing attributes (besides `href`) that can load or execute content.
6074
+ */ const URL_ATTRIBUTES = [
6075
+ 'src',
6076
+ 'data',
6077
+ 'action',
6078
+ 'formaction',
6079
+ 'poster',
6080
+ 'background',
6081
+ 'xlink:href'
6082
+ ];
6083
+ /**
6084
+ * Tags that load their URL as a *document* (scripts inside run). An SVG data
6085
+ * URL is only an XSS vector here — as an `<img>` source it renders inertly.
6086
+ */ const DOCUMENT_EMBED_TAGS = new Set([
6087
+ 'iframe',
6088
+ 'frame',
6089
+ 'object',
6090
+ 'embed'
6091
+ ]);
6092
+ /**
6093
+ * Detects executable / script-bearing URL schemes. The attribute value is
6094
+ * already HTML-entity-decoded by `getAttribute`, so only whitespace and
6095
+ * control characters (which browsers ignore inside a scheme) need stripping.
6096
+ */ function isDangerousUrl(value, tagName) {
6097
+ // eslint-disable-next-line no-control-regex
6098
+ const normalized = value.replace(/[\u0000-\u0020]+/g, '').toLowerCase();
6099
+ if (/^(?:javascript|vbscript|livescript|mocha):/.test(normalized)) {
6100
+ return true;
6101
+ }
6102
+ if (/^data:(?:text\/html|application\/xhtml)/.test(normalized)) {
6103
+ return true;
6104
+ }
6105
+ return /^data:image\/svg/.test(normalized) && DOCUMENT_EMBED_TAGS.has(tagName);
6106
+ }
6051
6107
  function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
6052
6108
  safeJavaScriptLink: true,
6053
6109
  removeOnError: true
@@ -6065,6 +6121,22 @@ function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
6065
6121
  (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.attr)(elm, 'href', location.protocol + '//' + href);
6066
6122
  effected = true;
6067
6123
  }
6124
+ if (safeJavaScriptLink) {
6125
+ // `srcdoc` runs its content as a full HTML document — drop it entirely.
6126
+ if (elm.hasAttribute('srcdoc')) {
6127
+ (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.attr)(elm, 'srcdoc', null);
6128
+ effected = true;
6129
+ }
6130
+ // Strip executable schemes from any other URL-bearing attribute.
6131
+ const tagName = elm.nodeName.toLowerCase();
6132
+ for (const name of URL_ATTRIBUTES){
6133
+ const value = elm.getAttribute(name);
6134
+ if (value && isDangerousUrl(value, tagName)) {
6135
+ (0,jodit_core_helpers_utils__WEBPACK_IMPORTED_MODULE_1__.attr)(elm, name, null);
6136
+ effected = true;
6137
+ }
6138
+ }
6139
+ }
6068
6140
  return effected;
6069
6141
  }
6070
6142
 
@@ -6115,7 +6187,7 @@ const ALONE_TAGS = new Set([
6115
6187
  ]);
6116
6188
  /**
6117
6189
  * Extract plain text from HTML text
6118
- */ function stripTags(html, doc = document, exclude = null) {
6190
+ */ function stripTags(html, doc = document, exclude = null, blockBr = false) {
6119
6191
  const tmp = doc.createElement('div');
6120
6192
  if ((0,jodit_core_helpers_checker_is_string__WEBPACK_IMPORTED_MODULE_1__.isString)(html)) {
6121
6193
  tmp.innerHTML = html;
@@ -6129,7 +6201,7 @@ const ALONE_TAGS = new Set([
6129
6201
  }
6130
6202
  if (exclude && jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isTag(p, exclude)) {
6131
6203
  const tag = p.nodeName.toLowerCase();
6132
- const text = !jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isTag(p, ALONE_TAGS) ? `%%%jodit-${tag}%%%${stripTags(p.innerHTML, doc, exclude)}%%%/jodit-${tag}%%%` : `%%%jodit-single-${tag}%%%`;
6204
+ const text = !jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isTag(p, ALONE_TAGS) ? `%%%jodit-${tag}%%%${stripTags(p.innerHTML, doc, exclude, blockBr)}%%%/jodit-${tag}%%%` : `%%%jodit-single-${tag}%%%`;
6133
6205
  jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.before(p, doc.createTextNode(text));
6134
6206
  jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.safeRemove(p);
6135
6207
  return;
@@ -6146,7 +6218,11 @@ const ALONE_TAGS = new Set([
6146
6218
  return;
6147
6219
  }
6148
6220
  if (nx) {
6149
- pr.insertBefore(doc.createTextNode(' '), nx);
6221
+ // By default blocks are joined with a single space (single-line
6222
+ // plain text). When `blockBr` is set, separate them with a line
6223
+ // break instead, so paragraph structure survives — e.g. the
6224
+ // "Insert only Text" paste option. See #1232
6225
+ pr.insertBefore(doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '), nx);
6150
6226
  }
6151
6227
  });
6152
6228
  return restoreTags((0,jodit_core_helpers_string_trim__WEBPACK_IMPORTED_MODULE_2__.trim)(tmp.innerText));
@@ -7807,7 +7883,6 @@ function ConfigDeepFlatten(obj) {
7807
7883
  return url;
7808
7884
  }
7809
7885
  const parser = jodit_core_constants__WEBPACK_IMPORTED_MODULE_0__.globalDocument.createElement('a');
7810
- const pattern1 = /(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/g;
7811
7886
  parser.href = url;
7812
7887
  if (!width) {
7813
7888
  width = 400;
@@ -7819,19 +7894,41 @@ function ConfigDeepFlatten(obj) {
7819
7894
  switch(parser.hostname){
7820
7895
  case 'www.vimeo.com':
7821
7896
  case 'vimeo.com':
7822
- return pattern1.test(url) ? url.replace(pattern1, '<iframe width="' + width + '" height="' + height + '" src="' + protocol + '//player.vimeo.com/video/$1" frameborder="0" allowfullscreen></iframe>') : url;
7897
+ {
7898
+ // The numeric video id can be preceded by `channels/<name>/` or
7899
+ // `groups/<name>/videos/` and followed by tracking params (e.g.
7900
+ // `?share=copy`). Unlisted videos keep a hash right after the id
7901
+ // (`vimeo.com/<id>/<hash>`). Extract the id (+ hash) from the path
7902
+ // so all of those forms produce a valid embed. See #1209
7903
+ const segments = parser.pathname.split('/').filter(Boolean);
7904
+ const idIndex = segments.findIndex((s)=>/^\d+$/.test(s));
7905
+ if (idIndex === -1) {
7906
+ return url;
7907
+ }
7908
+ let path = segments[idIndex];
7909
+ const hash = segments[idIndex + 1];
7910
+ if (hash && idIndex === 0) {
7911
+ path += '/' + hash;
7912
+ }
7913
+ return '<iframe width="' + width + '" height="' + height + '" src="' + protocol + '//player.vimeo.com/video/' + path + '" frameborder="0" allowfullscreen></iframe>';
7914
+ }
7823
7915
  case 'youtube.com':
7824
7916
  case 'www.youtube.com':
7917
+ case 'm.youtube.com':
7918
+ case 'music.youtube.com':
7825
7919
  case 'youtu.be':
7826
7920
  case 'www.youtu.be':
7827
7921
  {
7828
- const query = parser.search ? (0,_parse_query__WEBPACK_IMPORTED_MODULE_2__.parseQuery)(parser.search) : {
7829
- v: parser.pathname.substring(1)
7830
- };
7831
- if (/^embed\/.*/.test(query.v)) {
7832
- query.v = query.v.substring(6);
7833
- }
7834
- return query.v ? '<iframe width="' + width + '" height="' + height + '" src="' + protocol + '//www.youtube.com/embed/' + query.v + '" frameborder="0" allowfullscreen></iframe>' : url;
7922
+ const query = parser.search ? (0,_parse_query__WEBPACK_IMPORTED_MODULE_2__.parseQuery)(parser.search) : {};
7923
+ // `youtube.com/watch` keeps the video id in the `v` query
7924
+ // parameter, while the short `youtu.be/<id>` links and the
7925
+ // `/embed/`, `/shorts/`, `/live/` paths keep it in the pathname.
7926
+ // Modern share urls add tracking params (e.g. `?si=`, `?t=`), so
7927
+ // the pathname must still be used as a fallback when there is no
7928
+ // `v`. See #1209
7929
+ let v = query.v || parser.pathname.substring(1);
7930
+ v = v.replace(/^(watch|embed|shorts|live|v)\//, '').replace(/\/$/, '');
7931
+ return v ? '<iframe width="' + width + '" height="' + height + '" src="' + protocol + '//www.youtube.com/embed/' + v + '" frameborder="0" allowfullscreen></iframe>' : url;
7835
7932
  }
7836
7933
  }
7837
7934
  return url;
@@ -17639,6 +17736,23 @@ class Jodit extends jodit_modules__WEBPACK_IMPORTED_MODULE_9__.ViewWithToolbar {
17639
17736
  }
17640
17737
  this.synchronizeValues();
17641
17738
  }
17739
+ }).on(this.ow, 'mouseup', (event)=>{
17740
+ if (this.o.readonly || this.__isSilentChange) {
17741
+ return;
17742
+ }
17743
+ // When a selection is started inside the editor and the
17744
+ // mouse button is released outside of it, the editable
17745
+ // area never receives the `mouseup` event, so the toolbar
17746
+ // state (active buttons) is not recalculated. Re-fire the
17747
+ // event manually for that case while the selection still
17748
+ // belongs to the editor. See #1251
17749
+ const target = event.target;
17750
+ const insideEditor = Boolean(target && (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.isNumber)(target.nodeType) && editor.contains(target));
17751
+ if (insideEditor || !this.s.isInsideArea) {
17752
+ return;
17753
+ }
17754
+ this.e.fire('changeSelection');
17755
+ this.synchronizeValues();
17642
17756
  });
17643
17757
  }
17644
17758
  fetch(url, options) {
@@ -24034,9 +24148,14 @@ jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.uploader = {
24034
24148
  insertImageAsBase64URI: false,
24035
24149
  imagesExtensions: [
24036
24150
  'jpg',
24037
- 'png',
24038
24151
  'jpeg',
24039
- 'gif'
24152
+ 'png',
24153
+ 'gif',
24154
+ 'webp',
24155
+ 'bmp',
24156
+ 'svg',
24157
+ 'tiff',
24158
+ 'avif'
24040
24159
  ],
24041
24160
  headers: null,
24042
24161
  data: null,
@@ -28259,8 +28378,9 @@ jodit_config__WEBPACK_IMPORTED_MODULE_3__.Config.prototype.controls.brush = {
28259
28378
  const update = (key, value)=>{
28260
28379
  if (value && value !== (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(editor.editor, key).toString()) {
28261
28380
  button.state.icon.fill = value;
28262
- return;
28381
+ return true;
28263
28382
  }
28383
+ return false;
28264
28384
  };
28265
28385
  if (color) {
28266
28386
  const mode = (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.dataBind)(button, 'color');
@@ -28270,8 +28390,16 @@ jodit_config__WEBPACK_IMPORTED_MODULE_3__.Config.prototype.controls.brush = {
28270
28390
  const current = editor.s.current();
28271
28391
  if (current && !button.state.disabled) {
28272
28392
  const currentBpx = jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, jodit_core_dom_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isElement, editor.editor) || editor.editor;
28273
- update('color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'color').toString());
28274
- update('background-color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'background-color').toString());
28393
+ // The icon's fill mirrors the current text/background color so the
28394
+ // button reflects the formatting under the caret. Both calls run so
28395
+ // that a background color (the second call) wins over the text color
28396
+ // when both are set. Keep the computed fill instead of resetting it
28397
+ // below. See #195, #182
28398
+ const hasColor = update('color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'color').toString());
28399
+ const hasBackground = update('background-color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'background-color').toString());
28400
+ if (hasColor || hasBackground) {
28401
+ return;
28402
+ }
28275
28403
  }
28276
28404
  button.state.icon.fill = '';
28277
28405
  button.state.activated = false;
@@ -30538,7 +30666,8 @@ jodit_config__WEBPACK_IMPORTED_MODULE_0__.Config.prototype.commandToHotkeys = {
30538
30666
  'cmd+shift+7'
30539
30667
  ],
30540
30668
  insertUnorderedList: [
30541
- 'ctrl+shift+8, cmd+shift+8'
30669
+ 'ctrl+shift+8',
30670
+ 'cmd+shift+8'
30542
30671
  ],
30543
30672
  selectall: [
30544
30673
  'ctrl+a',
@@ -32760,13 +32889,29 @@ jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.controls.indent = {
32760
32889
  jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.controls.outdent = {
32761
32890
  isDisabled: (editor)=>{
32762
32891
  const current = editor.s.current();
32763
- if (current) {
32764
- const currentBox = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isBlock, editor.editor);
32765
- if (currentBox) {
32766
- const arrow = (0,_helpers__WEBPACK_IMPORTED_MODULE_5__.getKey)(editor.o.direction, currentBox);
32767
- return !currentBox.style[arrow] || parseInt(currentBox.style[arrow], 10) <= 0;
32892
+ if (!current) {
32893
+ return true;
32894
+ }
32895
+ // A list item whose list is nested inside another list item can be
32896
+ // outdented (un-nested) by the `tab` plugin, even without an inline
32897
+ // indent margin. Keep the button enabled in that case. See #1247
32898
+ if (editor.o.tab?.tabInsideLiInsertNewList) {
32899
+ const li = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, 'li', editor.editor);
32900
+ if (li) {
32901
+ const list = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(li, [
32902
+ 'ul',
32903
+ 'ol'
32904
+ ], editor.editor);
32905
+ if (list && jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(list, 'li', editor.editor)) {
32906
+ return false;
32907
+ }
32768
32908
  }
32769
32909
  }
32910
+ const currentBox = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isBlock, editor.editor);
32911
+ if (currentBox) {
32912
+ const arrow = (0,_helpers__WEBPACK_IMPORTED_MODULE_5__.getKey)(editor.o.direction, currentBox);
32913
+ return !currentBox.style[arrow] || parseInt(currentBox.style[arrow], 10) <= 0;
32914
+ }
32770
32915
  return true;
32771
32916
  },
32772
32917
  tooltip: 'Decrease Indent'
@@ -35284,7 +35429,7 @@ class pasteFromWord extends jodit_core_plugin__WEBPACK_IMPORTED_MODULE_6__.Plugi
35284
35429
  }
35285
35430
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_ONLY_TEXT:
35286
35431
  {
35287
- html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.stripTags)((0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.cleanFromWord)(html));
35432
+ html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.stripTags)((0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.cleanFromWord)(html), this.j.ed, new Set(this.j.o.pasteExcludeStripTags), this.j.o.nl2brInPlainText);
35288
35433
  break;
35289
35434
  }
35290
35435
  }
@@ -35839,10 +35984,15 @@ jodit_config__WEBPACK_IMPORTED_MODULE_1__.Config.prototype.controls.paste = {
35839
35984
  html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.cleanFromWord)(html);
35840
35985
  break;
35841
35986
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_ONLY_TEXT:
35842
- html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.stripTags)(html, this.j.ed, new Set(this.j.o.pasteExcludeStripTags));
35987
+ html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.stripTags)(html, this.j.ed, new Set(this.j.o.pasteExcludeStripTags), this.j.o.nl2brInPlainText);
35843
35988
  break;
35844
35989
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_AS_TEXT:
35845
35990
  html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.htmlspecialchars)(html);
35991
+ // Keep the source line breaks instead of letting the raw
35992
+ // newlines collapse into spaces when rendered. See #1093
35993
+ if (this.j.o.nl2brInPlainText) {
35994
+ html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.nl2br)(html);
35995
+ }
35846
35996
  break;
35847
35997
  default:
35848
35998
  {