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
package/es2021/jodit.js CHANGED
@@ -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) {
@@ -24054,9 +24168,14 @@ jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.uploader = {
24054
24168
  insertImageAsBase64URI: false,
24055
24169
  imagesExtensions: [
24056
24170
  'jpg',
24057
- 'png',
24058
24171
  'jpeg',
24059
- 'gif'
24172
+ 'png',
24173
+ 'gif',
24174
+ 'webp',
24175
+ 'bmp',
24176
+ 'svg',
24177
+ 'tiff',
24178
+ 'avif'
24060
24179
  ],
24061
24180
  headers: null,
24062
24181
  data: null,
@@ -28329,8 +28448,9 @@ jodit_config__WEBPACK_IMPORTED_MODULE_3__.Config.prototype.controls.brush = {
28329
28448
  const update = (key, value)=>{
28330
28449
  if (value && value !== (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(editor.editor, key).toString()) {
28331
28450
  button.state.icon.fill = value;
28332
- return;
28451
+ return true;
28333
28452
  }
28453
+ return false;
28334
28454
  };
28335
28455
  if (color) {
28336
28456
  const mode = (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.dataBind)(button, 'color');
@@ -28340,8 +28460,16 @@ jodit_config__WEBPACK_IMPORTED_MODULE_3__.Config.prototype.controls.brush = {
28340
28460
  const current = editor.s.current();
28341
28461
  if (current && !button.state.disabled) {
28342
28462
  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;
28343
- update('color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'color').toString());
28344
- update('background-color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'background-color').toString());
28463
+ // The icon's fill mirrors the current text/background color so the
28464
+ // button reflects the formatting under the caret. Both calls run so
28465
+ // that a background color (the second call) wins over the text color
28466
+ // when both are set. Keep the computed fill instead of resetting it
28467
+ // below. See #195, #182
28468
+ const hasColor = update('color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'color').toString());
28469
+ const hasBackground = update('background-color', (0,jodit_core_helpers___WEBPACK_IMPORTED_MODULE_1__.css)(currentBpx, 'background-color').toString());
28470
+ if (hasColor || hasBackground) {
28471
+ return;
28472
+ }
28345
28473
  }
28346
28474
  button.state.icon.fill = '';
28347
28475
  button.state.activated = false;
@@ -30942,7 +31070,8 @@ jodit_config__WEBPACK_IMPORTED_MODULE_0__.Config.prototype.commandToHotkeys = {
30942
31070
  'cmd+shift+7'
30943
31071
  ],
30944
31072
  insertUnorderedList: [
30945
- 'ctrl+shift+8, cmd+shift+8'
31073
+ 'ctrl+shift+8',
31074
+ 'cmd+shift+8'
30946
31075
  ],
30947
31076
  selectall: [
30948
31077
  'ctrl+a',
@@ -33164,13 +33293,29 @@ jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.controls.indent = {
33164
33293
  jodit_config__WEBPACK_IMPORTED_MODULE_2__.Config.prototype.controls.outdent = {
33165
33294
  isDisabled: (editor)=>{
33166
33295
  const current = editor.s.current();
33167
- if (current) {
33168
- const currentBox = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isBlock, editor.editor);
33169
- if (currentBox) {
33170
- const arrow = (0,_helpers__WEBPACK_IMPORTED_MODULE_5__.getKey)(editor.o.direction, currentBox);
33171
- return !currentBox.style[arrow] || parseInt(currentBox.style[arrow], 10) <= 0;
33296
+ if (!current) {
33297
+ return true;
33298
+ }
33299
+ // A list item whose list is nested inside another list item can be
33300
+ // outdented (un-nested) by the `tab` plugin, even without an inline
33301
+ // indent margin. Keep the button enabled in that case. See #1247
33302
+ if (editor.o.tab?.tabInsideLiInsertNewList) {
33303
+ const li = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, 'li', editor.editor);
33304
+ if (li) {
33305
+ const list = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(li, [
33306
+ 'ul',
33307
+ 'ol'
33308
+ ], editor.editor);
33309
+ if (list && jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(list, 'li', editor.editor)) {
33310
+ return false;
33311
+ }
33172
33312
  }
33173
33313
  }
33314
+ const currentBox = jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.closest(current, jodit_core_dom__WEBPACK_IMPORTED_MODULE_0__.Dom.isBlock, editor.editor);
33315
+ if (currentBox) {
33316
+ const arrow = (0,_helpers__WEBPACK_IMPORTED_MODULE_5__.getKey)(editor.o.direction, currentBox);
33317
+ return !currentBox.style[arrow] || parseInt(currentBox.style[arrow], 10) <= 0;
33318
+ }
33174
33319
  return true;
33175
33320
  },
33176
33321
  tooltip: 'Decrease Indent'
@@ -36038,7 +36183,7 @@ class pasteFromWord extends jodit_core_plugin__WEBPACK_IMPORTED_MODULE_6__.Plugi
36038
36183
  }
36039
36184
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_ONLY_TEXT:
36040
36185
  {
36041
- html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.stripTags)((0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_5__.cleanFromWord)(html));
36186
+ 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);
36042
36187
  break;
36043
36188
  }
36044
36189
  }
@@ -36593,10 +36738,15 @@ jodit_config__WEBPACK_IMPORTED_MODULE_1__.Config.prototype.controls.paste = {
36593
36738
  html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.cleanFromWord)(html);
36594
36739
  break;
36595
36740
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_ONLY_TEXT:
36596
- html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.stripTags)(html, this.j.ed, new Set(this.j.o.pasteExcludeStripTags));
36741
+ html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.stripTags)(html, this.j.ed, new Set(this.j.o.pasteExcludeStripTags), this.j.o.nl2brInPlainText);
36597
36742
  break;
36598
36743
  case jodit_core_constants__WEBPACK_IMPORTED_MODULE_2__.INSERT_AS_TEXT:
36599
36744
  html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.htmlspecialchars)(html);
36745
+ // Keep the source line breaks instead of letting the raw
36746
+ // newlines collapse into spaces when rendered. See #1093
36747
+ if (this.j.o.nl2brInPlainText) {
36748
+ html = (0,jodit_core_helpers__WEBPACK_IMPORTED_MODULE_6__.nl2br)(html);
36749
+ }
36600
36750
  break;
36601
36751
  default:
36602
36752
  {