roosterjs 9.53.0 → 9.55.0

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/dist/rooster.js CHANGED
@@ -6752,7 +6752,7 @@ var formatSegmentWithContentModel_1 = __webpack_require__(/*! ../utils/formatSeg
6752
6752
  */
6753
6753
  function changeCapitalization(editor, capitalization, language) {
6754
6754
  editor.focus();
6755
- (0, formatSegmentWithContentModel_1.formatSegmentWithContentModel)(editor, 'changeCapitalization', function (_, __, segment) {
6755
+ (0, formatSegmentWithContentModel_1.formatSegmentWithContentModel)(editor, 'changeCapitalization', function (_, __, segment, paragraph) {
6756
6756
  if ((segment === null || segment === void 0 ? void 0 : segment.segmentType) == 'Text') {
6757
6757
  switch (capitalization) {
6758
6758
  case 'lowerCase':
@@ -6763,7 +6763,13 @@ function changeCapitalization(editor, capitalization, language) {
6763
6763
  break;
6764
6764
  case 'capitalize':
6765
6765
  var wordArray = segment.text.toLocaleLowerCase(language).split(' ');
6766
- for (var i = 0; i < wordArray.length; i++) {
6766
+ // When a collapsed selection is in the middle of a word, the word is split
6767
+ // into multiple text segments around the selection marker. In that case the
6768
+ // first segment of the word continues from a previous segment, so its first
6769
+ // word must not be capitalized to avoid results like "HeLlo" for "he|llo".
6770
+ var precedingChar = getPrecedingCharacter(paragraph, segment);
6771
+ var startIndex = precedingChar && precedingChar != ' ' ? 1 : 0;
6772
+ for (var i = startIndex; i < wordArray.length; i++) {
6767
6773
  wordArray[i] =
6768
6774
  wordArray[i].charAt(0).toLocaleUpperCase(language) +
6769
6775
  wordArray[i].slice(1);
@@ -6787,6 +6793,30 @@ function changeCapitalization(editor, capitalization, language) {
6787
6793
  });
6788
6794
  }
6789
6795
  exports.changeCapitalization = changeCapitalization;
6796
+ /**
6797
+ * Get the character immediately preceding the given segment within its paragraph, skipping
6798
+ * selection markers (which carry no text). Returns an empty string if there is no preceding
6799
+ * text character (e.g. the segment starts the paragraph or follows a non-text segment).
6800
+ */
6801
+ function getPrecedingCharacter(paragraph, segment) {
6802
+ if (!paragraph) {
6803
+ return '';
6804
+ }
6805
+ var index = paragraph.segments.indexOf(segment);
6806
+ for (var i = index - 1; i >= 0; i--) {
6807
+ var previous = paragraph.segments[i];
6808
+ if (previous.segmentType == 'SelectionMarker') {
6809
+ continue;
6810
+ }
6811
+ else if (previous.segmentType == 'Text' && previous.text.length > 0) {
6812
+ return previous.text[previous.text.length - 1];
6813
+ }
6814
+ else {
6815
+ break;
6816
+ }
6817
+ }
6818
+ return '';
6819
+ }
6790
6820
 
6791
6821
 
6792
6822
  /***/ },
@@ -8020,6 +8050,15 @@ function formatInsertPointWithContentModel(editor, insertPoint, callback, option
8020
8050
  textWithSelection: getShadowTextProcessor(bundle),
8021
8051
  },
8022
8052
  tryGetFromCache: false,
8053
+ // When an element carries "container level" styles such as margin or padding, we first
8054
+ // wrap it in a FormatContainer. After all its child nodes are processed, we decide whether
8055
+ // to keep the FormatContainer or fall back to a plain paragraph when it only wraps a single
8056
+ // paragraph. However, formatInsertPointWithContentModel persists the Content Model group path
8057
+ // during processing so the later formatting callback can still use it (see the
8058
+ // DomToModelContextWithPath interface below). If the FormatContainer falls back to a paragraph,
8059
+ // it is removed from the model and the persisted path becomes invalid. To keep the path valid,
8060
+ // we skip the fallback check here and always keep the FormatContainer when one is needed.
8061
+ skipFormatContainerFallbackCheck: true,
8023
8062
  });
8024
8063
  }
8025
8064
  exports.formatInsertPointWithContentModel = formatInsertPointWithContentModel;
@@ -9340,21 +9379,11 @@ exports.generatePasteOptionFromPlugins = generatePasteOptionFromPlugins;
9340
9379
  "use strict";
9341
9380
 
9342
9381
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9343
- exports.mergePasteContent = exports.cloneModelForPaste = void 0;
9382
+ exports.mergePasteContent = void 0;
9344
9383
  var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
9345
9384
  var createDomToModelContextForSanitizing_1 = __webpack_require__(/*! ../createModelFromHtml/createDomToModelContextForSanitizing */ "./packages/roosterjs-content-model-core/lib/command/createModelFromHtml/createDomToModelContextForSanitizing.ts");
9346
9385
  var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "./packages/roosterjs-content-model-dom/lib/index.ts");
9347
9386
  var BlackColor = 'rgb(0,0,0)';
9348
- var CloneOption = {
9349
- includeCachedElement: function (node, type) { return (type == 'cache' ? undefined : node); },
9350
- };
9351
- /**
9352
- * @internal
9353
- */
9354
- function cloneModelForPaste(model) {
9355
- return (0, roosterjs_content_model_dom_1.cloneModel)(model, CloneOption);
9356
- }
9357
- exports.cloneModelForPaste = cloneModelForPaste;
9358
9387
  /**
9359
9388
  * @internal
9360
9389
  */
@@ -9362,7 +9391,7 @@ function mergePasteContent(editor, eventResult, isFirstPaste) {
9362
9391
  var fragment = eventResult.fragment, domToModelOption = eventResult.domToModelOption, customizedMerge = eventResult.customizedMerge, pasteType = eventResult.pasteType, clipboardData = eventResult.clipboardData, containsBlockElements = eventResult.containsBlockElements;
9363
9392
  editor.formatContentModel(function (model, context) {
9364
9393
  if (!isFirstPaste && clipboardData.modelBeforePaste) {
9365
- var clonedModel = cloneModelForPaste(clipboardData.modelBeforePaste);
9394
+ var clonedModel = (0, roosterjs_content_model_dom_1.cloneModelForPaste)(clipboardData.modelBeforePaste);
9366
9395
  model.blocks = clonedModel.blocks;
9367
9396
  }
9368
9397
  var domToModelContext = (0, createDomToModelContextForSanitizing_1.createDomToModelContextForSanitizing)(editor.getDocument(), undefined /*defaultFormat*/, editor.getEnvironment().domToModelSettings.customized, domToModelOption, editor.getDOMHelper());
@@ -9441,10 +9470,11 @@ function getLastSegmentFormat(pasteModel) {
9441
9470
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9442
9471
  exports.paste = void 0;
9443
9472
  var cleanHtmlComments_1 = __webpack_require__(/*! ./cleanHtmlComments */ "./packages/roosterjs-content-model-core/lib/command/paste/cleanHtmlComments.ts");
9444
- var mergePasteContent_1 = __webpack_require__(/*! ./mergePasteContent */ "./packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts");
9473
+ var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "./packages/roosterjs-content-model-dom/lib/index.ts");
9445
9474
  var convertInlineCss_1 = __webpack_require__(/*! ../createModelFromHtml/convertInlineCss */ "./packages/roosterjs-content-model-core/lib/command/createModelFromHtml/convertInlineCss.ts");
9446
9475
  var createPasteFragment_1 = __webpack_require__(/*! ./createPasteFragment */ "./packages/roosterjs-content-model-core/lib/command/paste/createPasteFragment.ts");
9447
9476
  var generatePasteOptionFromPlugins_1 = __webpack_require__(/*! ./generatePasteOptionFromPlugins */ "./packages/roosterjs-content-model-core/lib/command/paste/generatePasteOptionFromPlugins.ts");
9477
+ var mergePasteContent_1 = __webpack_require__(/*! ./mergePasteContent */ "./packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts");
9448
9478
  var retrieveHtmlInfo_1 = __webpack_require__(/*! ./retrieveHtmlInfo */ "./packages/roosterjs-content-model-core/lib/command/paste/retrieveHtmlInfo.ts");
9449
9479
  /**
9450
9480
  * Paste into editor using a clipboardData object
@@ -9460,7 +9490,7 @@ function paste(editor, clipboardData, pasteTypeOrGetter) {
9460
9490
  if (!clipboardData.modelBeforePaste) {
9461
9491
  isFirstPaste = true;
9462
9492
  editor.formatContentModel(function (model) {
9463
- clipboardData.modelBeforePaste = (0, mergePasteContent_1.cloneModelForPaste)(model);
9493
+ clipboardData.modelBeforePaste = (0, roosterjs_content_model_dom_1.cloneModelForPaste)(model);
9464
9494
  return false;
9465
9495
  });
9466
9496
  }
@@ -10104,6 +10134,9 @@ var createContentModel = function (core, option, selectionOverride) {
10104
10134
  var domToModelContext = option
10105
10135
  ? (0, roosterjs_content_model_dom_1.createDomToModelContext)(editorContext, settings.builtIn, settings.customized, option)
10106
10136
  : (0, roosterjs_content_model_dom_1.createDomToModelContextWithConfig)(settings.calculated, editorContext);
10137
+ if (option === null || option === void 0 ? void 0 : option.skipFormatContainerFallbackCheck) {
10138
+ domToModelContext.skipFormatContainerFallbackCheck = true;
10139
+ }
10107
10140
  if (selection) {
10108
10141
  domToModelContext.selection = selection;
10109
10142
  }
@@ -11575,6 +11608,11 @@ var CachePlugin = /** @class */ (function () {
11575
11608
  _this.invalidateCache();
11576
11609
  }
11577
11610
  break;
11611
+ case 'attribute':
11612
+ if (!_this.state.domIndexer.reconcileImageAttribute(mutation.element, mutation.attributeName)) {
11613
+ _this.invalidateCache();
11614
+ }
11615
+ break;
11578
11616
  case 'unknown':
11579
11617
  _this.invalidateCache();
11580
11618
  break;
@@ -12106,6 +12144,33 @@ var DomIndexerImpl = /** @class */ (function () {
12106
12144
  return false;
12107
12145
  }
12108
12146
  };
12147
+ DomIndexerImpl.prototype.reconcileImageAttribute = function (element, attributeName) {
12148
+ var _a, _b;
12149
+ if ((0, roosterjs_content_model_dom_1.isElementOfType)(element, 'img')) {
12150
+ var image = (_a = getIndexedSegmentItem(element)) === null || _a === void 0 ? void 0 : _a.segments[0];
12151
+ if ((image === null || image === void 0 ? void 0 : image.segmentType) == 'Image') {
12152
+ if (attributeName == 'src') {
12153
+ // Use getAttribute('src') instead of retrieving src directly, in case the src
12154
+ // has port and may be stripped by browser. This matches imageProcessor.
12155
+ image.src = (_b = element.getAttribute('src')) !== null && _b !== void 0 ? _b : '';
12156
+ return true;
12157
+ }
12158
+ else if (attributeName.indexOf('data-') == 0) {
12159
+ // A data-* attribute may be added, modified or removed. Rebuild the whole
12160
+ // dataset from DOM to keep it in sync, the same way imageProcessor builds it.
12161
+ var dataset_1 = image.dataset;
12162
+ (0, roosterjs_content_model_dom_1.getObjectKeys)(dataset_1).forEach(function (key) {
12163
+ delete dataset_1[key];
12164
+ });
12165
+ (0, roosterjs_content_model_dom_1.getObjectKeys)(element.dataset).forEach(function (key) {
12166
+ dataset_1[key] = element.dataset[key] || '';
12167
+ });
12168
+ return true;
12169
+ }
12170
+ }
12171
+ }
12172
+ return false;
12173
+ };
12109
12174
  DomIndexerImpl.prototype.onBlockEntityDelimiter = function (node, entity, parent) {
12110
12175
  if ((0, roosterjs_content_model_dom_1.isNodeOfType)(node, 'ELEMENT_NODE') && (0, roosterjs_content_model_dom_1.isEntityDelimiter)(node) && node.firstChild) {
12111
12176
  var indexedDelimiter = node.firstChild;
@@ -12421,6 +12486,16 @@ var TextMutationObserverImpl = /** @class */ (function () {
12421
12486
  (0, roosterjs_content_model_dom_1.isNodeOfType)(target, 'ELEMENT_NODE')) {
12422
12487
  _this.onMutation({ type: 'elementId', element: target });
12423
12488
  }
12489
+ else if (mutation.attributeName &&
12490
+ (0, roosterjs_content_model_dom_1.isNodeOfType)(target, 'ELEMENT_NODE') &&
12491
+ (mutation.attributeName == 'src' ||
12492
+ mutation.attributeName.indexOf('data-') == 0)) {
12493
+ _this.onMutation({
12494
+ type: 'attribute',
12495
+ element: target,
12496
+ attributeName: mutation.attributeName,
12497
+ });
12498
+ }
12424
12499
  else {
12425
12500
  // We cannot handle attributes changes on editor content for now
12426
12501
  canHandle = false;
@@ -12700,7 +12775,7 @@ var CopyPastePlugin = /** @class */ (function () {
12700
12775
  var dataTransfer = event.clipboardData;
12701
12776
  if (shouldPreventDefaultPaste(dataTransfer, editor_1)) {
12702
12777
  event.preventDefault();
12703
- (0, roosterjs_content_model_dom_1.extractClipboardItems)((0, roosterjs_content_model_dom_1.toArray)(dataTransfer.items), _this.state.allowedCustomPasteType).then(function (clipboardData) {
12778
+ (0, roosterjs_content_model_dom_1.extractClipboardItems)((0, roosterjs_content_model_dom_1.toArray)(dataTransfer.items), _this.state.allowedCustomPasteType, true /* isPasteNative */).then(function (clipboardData) {
12704
12779
  if (!editor_1.isDisposed()) {
12705
12780
  (0, paste_1.paste)(editor_1, clipboardData, _this.state.defaultPasteType);
12706
12781
  }
@@ -18137,7 +18212,9 @@ var formatContainerProcessorInternal = function (group, element, context, forceF
18137
18212
  if (element.style.fontSize && parseInt(element.style.fontSize) == 0) {
18138
18213
  formatContainer.zeroFontSize = true;
18139
18214
  }
18140
- if (shouldFallbackToParagraph(formatContainer) && !forceFormatContainer) {
18215
+ if (!context.skipFormatContainerFallbackCheck &&
18216
+ shouldFallbackToParagraph(formatContainer) &&
18217
+ !forceFormatContainer) {
18141
18218
  // For DIV container that only has one paragraph child, container style can be merged into paragraph
18142
18219
  // and no need to have this container
18143
18220
  var paragraph = formatContainer.blocks[0];
@@ -19718,7 +19795,7 @@ var ContentHandlers = (_a = {},
19718
19795
  * @param allowedCustomPasteType Allowed custom content type when paste besides text/plain, text/html and images
19719
19796
  Only text types are supported, and do not add "text/" prefix to the type values
19720
19797
  */
19721
- function extractClipboardItems(items, allowedCustomPasteType) {
19798
+ function extractClipboardItems(items, allowedCustomPasteType, isPasteNative) {
19722
19799
  var data = {
19723
19800
  types: [],
19724
19801
  text: '',
@@ -19726,7 +19803,7 @@ function extractClipboardItems(items, allowedCustomPasteType) {
19726
19803
  files: [],
19727
19804
  rawHtml: null,
19728
19805
  customValues: {},
19729
- pasteNativeEvent: true,
19806
+ pasteNativeEvent: !!isPasteNative,
19730
19807
  };
19731
19808
  return Promise.all((items || []).map(function (item) {
19732
19809
  var type = item.type;
@@ -20667,6 +20744,7 @@ function pruneUnselectedModel(model) {
20667
20744
  exports.pruneUnselectedModel = pruneUnselectedModel;
20668
20745
  function pruneUnselectedModelInternal(model, isSelectionAfterElement) {
20669
20746
  var e_1, _a, _b;
20747
+ var _c;
20670
20748
  for (var index = model.blocks.length - 1; index >= 0; index--) {
20671
20749
  var block = model.blocks[index];
20672
20750
  switch (block.blockType) {
@@ -20690,15 +20768,17 @@ function pruneUnselectedModelInternal(model, isSelectionAfterElement) {
20690
20768
  case 'Paragraph':
20691
20769
  var newSegments = [];
20692
20770
  try {
20693
- for (var _c = (e_1 = void 0, (0, tslib_1.__values)(block.segments)), _d = _c.next(); !_d.done; _d = _c.next()) {
20694
- var segment = _d.value;
20771
+ for (var _d = (e_1 = void 0, (0, tslib_1.__values)(block.segments)), _e = _d.next(); !_e.done; _e = _d.next()) {
20772
+ var segment = _e.value;
20773
+ var isSelectedOrUndeletable = segment.isSelected || ((_c = segment.link) === null || _c === void 0 ? void 0 : _c.format.undeletable);
20695
20774
  if (segment.segmentType == 'General') {
20696
20775
  pruneUnselectedModel(segment);
20697
- if (segment.blocks.length > 0 || segment.isSelected) {
20776
+ if (segment.blocks.length > 0 || isSelectedOrUndeletable) {
20698
20777
  newSegments.push(segment);
20699
20778
  }
20700
20779
  }
20701
- else if (segment.isSelected && segment.segmentType != 'SelectionMarker') {
20780
+ else if (isSelectedOrUndeletable &&
20781
+ segment.segmentType != 'SelectionMarker') {
20702
20782
  newSegments.push(segment);
20703
20783
  }
20704
20784
  }
@@ -20706,7 +20786,7 @@ function pruneUnselectedModelInternal(model, isSelectionAfterElement) {
20706
20786
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
20707
20787
  finally {
20708
20788
  try {
20709
- if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
20789
+ if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
20710
20790
  }
20711
20791
  finally { if (e_1) throw e_1.error; }
20712
20792
  }
@@ -23679,8 +23759,8 @@ exports.shouldSetValue = shouldSetValue;
23679
23759
  Object.defineProperty(exports, "__esModule", ({ value: true }));
23680
23760
  exports.createImage = exports.createText = exports.createTableCell = exports.createTable = exports.createSelectionMarker = exports.createParagraph = exports.createFormatContainer = exports.createListItem = exports.createBr = exports.isLinkUndeletable = exports.setLinkUndeletable = exports.scrollRectIntoView = exports.normalizeRect = exports.isWhiteSpacePreserved = exports.reuseCachedElement = exports.findClosestBlockEntityContainer = exports.isBlockEntityContainer = exports.isEntityDelimiter = exports.addDelimiters = exports.generateEntityClassNames = exports.parseEntityFormat = exports.getAllEntityWrappers = exports.findClosestEntityWrapper = exports.isEntityElement = exports.unwrap = exports.wrap = exports.wrapAllChildNodes = exports.moveChildNodes = exports.toArray = exports.getSafeIdSelector = exports.getObjectKeys = exports.isElementOfType = exports.isNodeOfType = exports.hasMetadata = exports.getMetadata = exports.updateMetadata = exports.buildSelectionMarker = exports.isBlockElement = exports.areSameFormats = exports.parseFormat = exports.getRegularSelectionOffsets = exports.formatContainerProcessor = exports.tableProcessor = exports.entityProcessor = exports.processChildNode = exports.handleRegularSelection = exports.childProcessor = exports.contentModelToText = exports.contentModelToDom = exports.domToContentModel = void 0;
23681
23761
  exports.trimModelForSelection = exports.getDOMInsertPointRect = exports.getSelectionRootNode = exports.isBold = exports.createModelToDomConfig = exports.createModelToDomContextWithConfig = exports.createModelToDomContext = exports.createDomToModelConfig = exports.createDomToModelContextWithConfig = exports.createDomToModelContext = exports.defaultGenerateColorKey = exports.parseColor = exports.setColor = exports.getColor = exports.DeprecatedColors = exports.BorderKeys = exports.parseValueWithUnit = exports.getAutoListStyleType = exports.getOrderedListNumberStr = exports.ParagraphFormats = exports.ListFormatsToMove = exports.ListFormatsToKeep = exports.ListFormats = exports.copyFormat = exports.setParagraphNotImplicit = exports.normalizeSegmentFormat = exports.mergeTextSegments = exports.normalizeSingleSegment = exports.isEmpty = exports.addSegment = exports.unwrapBlock = exports.isGeneralSegment = exports.normalizeContentModel = exports.normalizeParagraph = exports.addTextSegment = exports.addLink = exports.addCode = exports.addBlock = exports.mutateSegment = exports.mutateSegments = exports.mutateBlock = exports.createTableRow = exports.createEmptyModel = exports.createListLevel = exports.createDivider = exports.createEntity = exports.createGeneralBlock = exports.createGeneralSegment = exports.createParagraphDecorator = exports.createContentModelDocument = void 0;
23682
- exports.runEditSteps = exports.getClosestAncestorBlockGroupIndex = exports.getSegmentTextFormat = exports.getListStyleTypeFromString = exports.retrieveModelFormatState = exports.setTableCellBackgroundColor = exports.MIN_ALLOWED_TABLE_CELL_HEIGHT = exports.MIN_ALLOWED_TABLE_CELL_WIDTH = exports.normalizeTable = exports.setFirstColumnFormatBorders = exports.applyTableFormat = exports.deleteBlock = exports.deleteSegment = exports.deleteSelection = exports.mergeModel = exports.cloneModel = exports.setSelection = exports.hasSelectionInBlockGroup = exports.hasSelectionInSegment = exports.hasSelectionInBlock = exports.getSelectedCells = exports.getSelectedSegmentsAndParagraphs = exports.getSelectedSegments = exports.getSelectedParagraphs = exports.getOperationalBlocks = exports.getFirstSelectedTable = exports.getFirstSelectedListItem = exports.iterateSelections = exports.isBlockGroupOfType = exports.getRangesByText = exports.getImageState = exports.setImageState = exports.getParagraphMarker = exports.setParagraphMarker = exports.cacheGetEventData = exports.extractClipboardItems = exports.normalizeFontFamily = exports.transformColor = exports.retrieveDocumentMetadata = exports.readFile = exports.parseTableCells = exports.normalizeText = exports.isSpace = exports.isPunctuation = exports.extractBorderValues = exports.combineBorderValue = exports.getNodePositionFromEvent = exports.isCursorMovingKey = exports.isModifierKey = exports.isCharacterValue = void 0;
23683
- exports.EmptySegmentFormat = exports.UnorderedListStyleMap = exports.OrderedListStyleMap = exports.TableBorderFormat = exports.NumberingListType = exports.BulletListType = exports.ChangeSource = exports.ListMetadataDefinition = exports.getListMetadata = exports.updateListMetadata = exports.getTableMetadata = exports.updateTableMetadata = exports.getTableCellMetadata = exports.updateTableCellMetadata = exports.getImageMetadata = exports.updateImageMetadata = void 0;
23762
+ exports.getClosestAncestorBlockGroupIndex = exports.getSegmentTextFormat = exports.getListStyleTypeFromString = exports.retrieveModelFormatState = exports.setTableCellBackgroundColor = exports.MIN_ALLOWED_TABLE_CELL_HEIGHT = exports.MIN_ALLOWED_TABLE_CELL_WIDTH = exports.normalizeTable = exports.setFirstColumnFormatBorders = exports.applyTableFormat = exports.deleteBlock = exports.deleteSegment = exports.deleteSelection = exports.mergeModel = exports.cloneModelForPaste = exports.cloneModel = exports.setSelection = exports.hasSelectionInBlockGroup = exports.hasSelectionInSegment = exports.hasSelectionInBlock = exports.getSelectedCells = exports.getSelectedSegmentsAndParagraphs = exports.getSelectedSegments = exports.getSelectedParagraphs = exports.getOperationalBlocks = exports.getFirstSelectedTable = exports.getFirstSelectedListItem = exports.iterateSelections = exports.isBlockGroupOfType = exports.getRangesByText = exports.getImageState = exports.setImageState = exports.getParagraphMarker = exports.setParagraphMarker = exports.cacheGetEventData = exports.extractClipboardItems = exports.normalizeFontFamily = exports.transformColor = exports.retrieveDocumentMetadata = exports.readFile = exports.parseTableCells = exports.normalizeText = exports.isSpace = exports.isPunctuation = exports.extractBorderValues = exports.combineBorderValue = exports.getNodePositionFromEvent = exports.isCursorMovingKey = exports.isModifierKey = exports.isCharacterValue = void 0;
23763
+ exports.EmptySegmentFormat = exports.UnorderedListStyleMap = exports.OrderedListStyleMap = exports.TableBorderFormat = exports.NumberingListType = exports.BulletListType = exports.ChangeSource = exports.ListMetadataDefinition = exports.getListMetadata = exports.updateListMetadata = exports.getTableMetadata = exports.updateTableMetadata = exports.getTableCellMetadata = exports.updateTableCellMetadata = exports.getImageMetadata = exports.updateImageMetadata = exports.runEditSteps = void 0;
23684
23764
  var domToContentModel_1 = __webpack_require__(/*! ./domToModel/domToContentModel */ "./packages/roosterjs-content-model-dom/lib/domToModel/domToContentModel.ts");
23685
23765
  Object.defineProperty(exports, "domToContentModel", ({ enumerable: true, get: function () { return domToContentModel_1.domToContentModel; } }));
23686
23766
  var contentModelToDom_1 = __webpack_require__(/*! ./modelToDom/contentModelToDom */ "./packages/roosterjs-content-model-dom/lib/modelToDom/contentModelToDom.ts");
@@ -23911,6 +23991,7 @@ var setSelection_1 = __webpack_require__(/*! ./modelApi/selection/setSelection *
23911
23991
  Object.defineProperty(exports, "setSelection", ({ enumerable: true, get: function () { return setSelection_1.setSelection; } }));
23912
23992
  var cloneModel_1 = __webpack_require__(/*! ./modelApi/editing/cloneModel */ "./packages/roosterjs-content-model-dom/lib/modelApi/editing/cloneModel.ts");
23913
23993
  Object.defineProperty(exports, "cloneModel", ({ enumerable: true, get: function () { return cloneModel_1.cloneModel; } }));
23994
+ Object.defineProperty(exports, "cloneModelForPaste", ({ enumerable: true, get: function () { return cloneModel_1.cloneModelForPaste; } }));
23914
23995
  var mergeModel_1 = __webpack_require__(/*! ./modelApi/editing/mergeModel */ "./packages/roosterjs-content-model-dom/lib/modelApi/editing/mergeModel.ts");
23915
23996
  Object.defineProperty(exports, "mergeModel", ({ enumerable: true, get: function () { return mergeModel_1.mergeModel; } }));
23916
23997
  var deleteSelection_1 = __webpack_require__(/*! ./modelApi/editing/deleteSelection */ "./packages/roosterjs-content-model-dom/lib/modelApi/editing/deleteSelection.ts");
@@ -24740,7 +24821,34 @@ exports.normalizeAllSegments = normalizeAllSegments;
24740
24821
  function normalizeSingleSegment(paragraph, segment, ignoreTrailingSpaces) {
24741
24822
  if (ignoreTrailingSpaces === void 0) { ignoreTrailingSpaces = false; }
24742
24823
  var context = resetNormalizeSegmentContext();
24824
+ var index = paragraph.segments.indexOf(segment);
24743
24825
  context.ignoreTrailingSpaces = ignoreTrailingSpaces;
24826
+ // Search backward for the nearest non-empty text segment (skipping SelectionMarkers and empty text),
24827
+ // to determine whether leading spaces of the current segment should be preserved.
24828
+ // If the previous text doesn't end with a space, keep the leading space to maintain word separation.
24829
+ for (var i = index - 1; i >= 0; i--) {
24830
+ var s = paragraph.segments[i];
24831
+ if (s.segmentType == 'Text') {
24832
+ if (s.text.length > 0) {
24833
+ if (s.text.substr(-1) != SPACE) {
24834
+ context.ignoreLeadingSpaces = false;
24835
+ }
24836
+ break;
24837
+ }
24838
+ }
24839
+ else if (s.segmentType == 'Image') {
24840
+ // If the previous segment is an image, we should keep the leading space of the current segment to maintain word separation.
24841
+ context.ignoreLeadingSpaces = false;
24842
+ break;
24843
+ }
24844
+ else if (s.segmentType == 'Br') {
24845
+ // If the previous segment is a line break, we should ignore the leading space of the current segment.
24846
+ context.ignoreLeadingSpaces = true;
24847
+ }
24848
+ else if (s.segmentType != 'SelectionMarker') {
24849
+ break;
24850
+ }
24851
+ }
24744
24852
  normalizeSegment(paragraph, segment, context);
24745
24853
  }
24746
24854
  exports.normalizeSingleSegment = normalizeSingleSegment;
@@ -24781,6 +24889,10 @@ function normalizeSegment(paragraph, segment, context) {
24781
24889
  context.ignoreLeadingSpaces = false;
24782
24890
  break;
24783
24891
  case 'Text':
24892
+ // Skip empty text segments to avoid affecting normalization context
24893
+ if (segment.text.length == 0) {
24894
+ break;
24895
+ }
24784
24896
  context.textSegments.push(segment);
24785
24897
  context.lastInlineSegment = segment;
24786
24898
  context.lastTextSegment = segment;
@@ -24788,9 +24900,9 @@ function normalizeSegment(paragraph, segment, context) {
24788
24900
  var last = segment.text.substr(-1);
24789
24901
  if (!(0, hasSpacesOnly_1.hasSpacesOnly)(segment.text)) {
24790
24902
  if (first == SPACE) {
24791
- // 1. Multiple leading space => single &nbsp; or empty (depends on if previous segment ends with space)
24903
+ // 1. Multiple leading space => single space or empty (depends on if previous segment ends with space)
24792
24904
  (0, mutate_1.mutateSegment)(paragraph, segment, function (textSegment) {
24793
- textSegment.text = textSegment.text.replace(LEADING_SPACE_REGEX, context.ignoreLeadingSpaces ? '' : NONE_BREAK_SPACE);
24905
+ textSegment.text = textSegment.text.replace(LEADING_SPACE_REGEX, context.ignoreLeadingSpaces ? '' : SPACE);
24794
24906
  });
24795
24907
  }
24796
24908
  if (last == SPACE) {
@@ -25889,8 +26001,20 @@ function getBorderStyleFromColor(color) {
25889
26001
  "use strict";
25890
26002
 
25891
26003
  Object.defineProperty(exports, "__esModule", ({ value: true }));
25892
- exports.cloneModel = void 0;
26004
+ exports.cloneModel = exports.cloneModelForPaste = void 0;
25893
26005
  var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
26006
+ var CloneOption = {
26007
+ includeCachedElement: function (node, type) { return (type == 'cache' ? undefined : node); },
26008
+ };
26009
+ /**
26010
+ * Clone a content model for paste operations, ensuring that cached elements are handled appropriately.
26011
+ * @param model The content model to clone
26012
+ * @returns A cloned content model suitable for paste operations
26013
+ */
26014
+ function cloneModelForPaste(model) {
26015
+ return cloneModel(model, CloneOption);
26016
+ }
26017
+ exports.cloneModelForPaste = cloneModelForPaste;
25894
26018
  /**
25895
26019
  * Clone a content model
25896
26020
  * @param model The content model to clone
@@ -28674,6 +28798,7 @@ function internalIterateSelections(path, callback, option, table, treatAllAsSele
28674
28798
 
28675
28799
  Object.defineProperty(exports, "__esModule", ({ value: true }));
28676
28800
  exports.setSelection = void 0;
28801
+ var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
28677
28802
  var isGeneralSegment_1 = __webpack_require__(/*! ../typeCheck/isGeneralSegment */ "./packages/roosterjs-content-model-dom/lib/modelApi/typeCheck/isGeneralSegment.ts");
28678
28803
  var mutate_1 = __webpack_require__(/*! ../common/mutate */ "./packages/roosterjs-content-model-dom/lib/modelApi/common/mutate.ts");
28679
28804
  /**
@@ -28711,6 +28836,7 @@ function setSelectionToBlockGroup(group, isInSelection, start, end) {
28711
28836
  });
28712
28837
  }
28713
28838
  function setSelectionToBlock(block, isInSelection, start, end) {
28839
+ var _a;
28714
28840
  switch (block.blockType) {
28715
28841
  case 'BlockGroup':
28716
28842
  return setSelectionToBlockGroup(block, isInSelection, start, end);
@@ -28731,16 +28857,31 @@ function setSelectionToBlock(block, isInSelection, start, end) {
28731
28857
  return isInSelection;
28732
28858
  });
28733
28859
  case 'Paragraph':
28734
- var segmentsToDelete_1 = [];
28860
+ var state_1 = {
28861
+ segmentsToDelete: [],
28862
+ boundaryMarkers: [],
28863
+ hasSelectedNonMarker: false,
28864
+ };
28735
28865
  block.segments.forEach(function (segment, i) {
28736
28866
  isInSelection = handleSelection(isInSelection, segment, start, end, function (isInSelection) {
28737
- return setSelectionToSegment(block, segment, isInSelection, segmentsToDelete_1, start, end, i);
28867
+ return setSelectionToSegment(block, segment, isInSelection, state_1, start, end, i);
28738
28868
  });
28739
28869
  });
28740
- if (segmentsToDelete_1.length > 0) {
28870
+ if (state_1.hasSelectedNonMarker) {
28871
+ // This paragraph contains a real (non-marker) selected segment, so any leading/trailing
28872
+ // selection marker of the range is redundant within this paragraph and can be removed.
28873
+ // We only do this within the same paragraph: a boundary marker at a paragraph edge must be
28874
+ // kept to distinguish "selection starts at the beginning of this line" from "selection
28875
+ // starts at the end of the previous line".
28876
+ (_a = state_1.segmentsToDelete).push.apply(_a, (0, tslib_1.__spreadArray)([], (0, tslib_1.__read)(state_1.boundaryMarkers), false));
28877
+ }
28878
+ if (state_1.segmentsToDelete.length > 0) {
28741
28879
  var mutablePara = (0, mutate_1.mutateBlock)(block);
28742
28880
  var index = void 0;
28743
- while ((index = segmentsToDelete_1.pop()) !== undefined) {
28881
+ // Sort ascending so the pop()-based splice below always removes the highest index first,
28882
+ // keeping the remaining indices valid (boundary markers may sit before queued deletions).
28883
+ state_1.segmentsToDelete.sort(function (a, b) { return a - b; });
28884
+ while ((index = state_1.segmentsToDelete.pop()) !== undefined) {
28744
28885
  if (index >= 0) {
28745
28886
  mutablePara.segments.splice(index, 1);
28746
28887
  }
@@ -28789,14 +28930,23 @@ function findCell(table, cell) {
28789
28930
  : -1;
28790
28931
  return { row: row, col: col };
28791
28932
  }
28792
- function setSelectionToSegment(paragraph, segment, isInSelection, segmentsToDelete, start, end, i) {
28933
+ function setSelectionToSegment(paragraph, segment, isInSelection, state, start, end, i) {
28934
+ if (segment.segmentType != 'SelectionMarker' && isInSelection) {
28935
+ state.hasSelectedNonMarker = true;
28936
+ }
28793
28937
  switch (segment.segmentType) {
28794
28938
  case 'SelectionMarker':
28795
28939
  if (!isInSelection || (segment != start && segment != end)) {
28796
28940
  // Delete the selection marker when
28797
28941
  // 1. It is not in selection any more. Or
28798
28942
  // 2. It is in middle of selection, so no need to have it
28799
- segmentsToDelete.push(i);
28943
+ state.segmentsToDelete.push(i);
28944
+ }
28945
+ else {
28946
+ // It is a leading/trailing selection marker of a range selection. Keep it for now, but
28947
+ // remember it so it can be removed later if this same paragraph also contains a real
28948
+ // (non-marker) selected segment, in which case the marker is redundant.
28949
+ state.boundaryMarkers.push(i);
28800
28950
  }
28801
28951
  return isInSelection;
28802
28952
  case 'General':
@@ -29237,7 +29387,7 @@ var handleBlockGroupChildren = function (doc, parent, group, context) {
29237
29387
  (_a = context.domIndexer) === null || _a === void 0 ? void 0 : _a.onBlockEntity(childBlock, group);
29238
29388
  }
29239
29389
  });
29240
- cleanUpNodeStack(listFormat.nodeStack, context);
29390
+ cleanUpNodeStack(listFormat.nodeStack, context, parent);
29241
29391
  // Remove all rest node if any since they don't appear in content model
29242
29392
  (0, cleanUpRestNodes_1.cleanUpRestNodes)(refNode, context.rewriteFromModel);
29243
29393
  }
@@ -29246,7 +29396,7 @@ var handleBlockGroupChildren = function (doc, parent, group, context) {
29246
29396
  }
29247
29397
  };
29248
29398
  exports.handleBlockGroupChildren = handleBlockGroupChildren;
29249
- function cleanUpNodeStack(nodeStack, context) {
29399
+ function cleanUpNodeStack(nodeStack, context, leavingParent) {
29250
29400
  var _a, _b;
29251
29401
  if (nodeStack.length > 0) {
29252
29402
  // Clear list stack, only run to nodeStack[1] because nodeStack[0] is the parent node
@@ -29254,6 +29404,13 @@ function cleanUpNodeStack(nodeStack, context) {
29254
29404
  var node = (_b = (_a = nodeStack.pop()) === null || _a === void 0 ? void 0 : _a.refNode) !== null && _b !== void 0 ? _b : null;
29255
29405
  (0, cleanUpRestNodes_1.cleanUpRestNodes)(node, context.rewriteFromModel);
29256
29406
  }
29407
+ if (leavingParent && nodeStack[0].node == leavingParent) {
29408
+ // When leaving a parent node that is the same with the root of node stack
29409
+ // It means the whole list node stack is being invalidated, so we clear it
29410
+ while (nodeStack.length > 0) {
29411
+ nodeStack.pop();
29412
+ }
29413
+ }
29257
29414
  }
29258
29415
  }
29259
29416
 
@@ -30642,11 +30799,17 @@ exports.MarkdownHeadings = {
30642
30799
  "use strict";
30643
30800
 
30644
30801
  Object.defineProperty(exports, "__esModule", ({ value: true }));
30645
- exports.convertContentModelToMarkdown = exports.convertMarkdownToContentModel = void 0;
30802
+ exports.MarkdownPastePlugin = exports.isPastedContentMarkdown = exports.isContentMarkdown = exports.convertContentModelToMarkdown = exports.convertMarkdownToContentModel = void 0;
30646
30803
  var convertMarkdownToContentModel_1 = __webpack_require__(/*! ./markdownToModel/convertMarkdownToContentModel */ "./packages/roosterjs-content-model-markdown/lib/markdownToModel/convertMarkdownToContentModel.ts");
30647
30804
  Object.defineProperty(exports, "convertMarkdownToContentModel", ({ enumerable: true, get: function () { return convertMarkdownToContentModel_1.convertMarkdownToContentModel; } }));
30648
30805
  var convertContentModelToMarkdown_1 = __webpack_require__(/*! ./modelToMarkdown/convertContentModelToMarkdown */ "./packages/roosterjs-content-model-markdown/lib/modelToMarkdown/convertContentModelToMarkdown.ts");
30649
30806
  Object.defineProperty(exports, "convertContentModelToMarkdown", ({ enumerable: true, get: function () { return convertContentModelToMarkdown_1.convertContentModelToMarkdown; } }));
30807
+ var isContentMarkdown_1 = __webpack_require__(/*! ./publicApi/isContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts");
30808
+ Object.defineProperty(exports, "isContentMarkdown", ({ enumerable: true, get: function () { return isContentMarkdown_1.isContentMarkdown; } }));
30809
+ var isPastedContentMarkdown_1 = __webpack_require__(/*! ./publicApi/isPastedContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts");
30810
+ Object.defineProperty(exports, "isPastedContentMarkdown", ({ enumerable: true, get: function () { return isPastedContentMarkdown_1.isPastedContentMarkdown; } }));
30811
+ var MarkdownPastePlugin_1 = __webpack_require__(/*! ./plugins/MarkdownPastePlugin */ "./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts");
30812
+ Object.defineProperty(exports, "MarkdownPastePlugin", ({ enumerable: true, get: function () { return MarkdownPastePlugin_1.MarkdownPastePlugin; } }));
30650
30813
 
30651
30814
 
30652
30815
  /***/ },
@@ -31336,6 +31499,13 @@ function parseInlineSegments(text, segments, state, link) {
31336
31499
  };
31337
31500
  while (i < text.length) {
31338
31501
  var remaining = text.substring(i);
31502
+ // Escaped character: a backslash followed by an ASCII punctuation character emits
31503
+ // that character literally (e.g. "\*" -> "*") and is never treated as a marker.
31504
+ if (text[i] === '\\' && i + 1 < text.length && isEscapable(text[i + 1])) {
31505
+ buffer += text[i + 1];
31506
+ i += 2;
31507
+ continue;
31508
+ }
31339
31509
  // Image: ![alt](url)
31340
31510
  var imgMatch = imagePattern.exec(remaining);
31341
31511
  if (imgMatch && isValidUrl(imgMatch[2])) {
@@ -31399,6 +31569,10 @@ function shouldToggleFormatting(text, index, marker, currentState) {
31399
31569
  function isWhitespace(char) {
31400
31570
  return /\s/.test(char);
31401
31571
  }
31572
+ function isEscapable(char) {
31573
+ // Per CommonMark, any ASCII punctuation character may be backslash-escaped.
31574
+ return /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(char);
31575
+ }
31402
31576
  function toggleFormatting(state, type) {
31403
31577
  switch (type) {
31404
31578
  case 'bold':
@@ -31902,6 +32076,278 @@ function modelProcessor(model, newLine) {
31902
32076
  exports.modelProcessor = modelProcessor;
31903
32077
 
31904
32078
 
32079
+ /***/ },
32080
+
32081
+ /***/ "./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts"
32082
+ /*!**************************************************************************************!*\
32083
+ !*** ./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts ***!
32084
+ \**************************************************************************************/
32085
+ (__unused_webpack_module, exports, __webpack_require__) {
32086
+
32087
+ "use strict";
32088
+
32089
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32090
+ exports.MarkdownPastePlugin = void 0;
32091
+ var convertMarkdownToContentModel_1 = __webpack_require__(/*! ../markdownToModel/convertMarkdownToContentModel */ "./packages/roosterjs-content-model-markdown/lib/markdownToModel/convertMarkdownToContentModel.ts");
32092
+ var isPastedContentMarkdown_1 = __webpack_require__(/*! ../publicApi/isPastedContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts");
32093
+ var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "./packages/roosterjs-content-model-dom/lib/index.ts");
32094
+ var DefaultOptions = {
32095
+ autoConversion: false,
32096
+ undoConversion: false,
32097
+ };
32098
+ /**
32099
+ * Markdown paste plugin. Handles the BeforePaste event and, when the pasted content
32100
+ * can be interpreted as markdown, converts the plain text into a Content Model and
32101
+ * pastes it as rich markdown content instead of the original clipboard HTML.
32102
+ */
32103
+ var MarkdownPastePlugin = /** @class */ (function () {
32104
+ /**
32105
+ * Construct a new instance of MarkdownPastePlugin
32106
+ * @param options Options to control the markdown paste behavior
32107
+ */
32108
+ function MarkdownPastePlugin(options) {
32109
+ this.editor = null;
32110
+ this.options = options !== null && options !== void 0 ? options : DefaultOptions;
32111
+ }
32112
+ /**
32113
+ * Get name of this plugin
32114
+ */
32115
+ MarkdownPastePlugin.prototype.getName = function () {
32116
+ return 'MarkdownPaste';
32117
+ };
32118
+ /**
32119
+ * The first method that editor will call to a plugin when editor is initializing.
32120
+ * It will pass in the editor instance, plugin should take this chance to save the
32121
+ * editor reference so that it can call to any editor method or format API later.
32122
+ * @param editor The editor object
32123
+ */
32124
+ MarkdownPastePlugin.prototype.initialize = function (editor) {
32125
+ this.editor = editor;
32126
+ };
32127
+ /**
32128
+ * The last method that editor will call to a plugin before it is disposed.
32129
+ * Plugin can take this chance to clear the reference to editor. After this method is
32130
+ * called, plugin should not call to any editor method since it will result in error.
32131
+ */
32132
+ MarkdownPastePlugin.prototype.dispose = function () {
32133
+ this.editor = null;
32134
+ };
32135
+ /**
32136
+ * Core method for a plugin. Once an event happens in editor, editor will call this
32137
+ * method of each plugin to handle the event as long as the event is not handled
32138
+ * exclusively by another plugin.
32139
+ * @param event The event to handle:
32140
+ */
32141
+ MarkdownPastePlugin.prototype.onPluginEvent = function (event) {
32142
+ if (!this.editor) {
32143
+ return;
32144
+ }
32145
+ if (event.eventType === 'contentChanged' &&
32146
+ event.source === roosterjs_content_model_dom_1.ChangeSource.Paste &&
32147
+ this.options.autoConversion &&
32148
+ event.data) {
32149
+ var clipboardData = event.data;
32150
+ var shouldConvert = clipboardData && clipboardData.pasteNativeEvent && this.options.autoConversion;
32151
+ if (shouldConvert &&
32152
+ (0, isPastedContentMarkdown_1.isPastedContentMarkdown)(this.editor, clipboardData) &&
32153
+ clipboardData.modelBeforePaste) {
32154
+ var modelBeforePaste_1 = (0, roosterjs_content_model_dom_1.cloneModelForPaste)(clipboardData.modelBeforePaste);
32155
+ (0, roosterjs_content_model_dom_1.mergeModel)(modelBeforePaste_1, (0, convertMarkdownToContentModel_1.convertMarkdownToContentModel)(clipboardData.text, { emptyLine: 'merge' }));
32156
+ if (this.options.undoConversion) {
32157
+ this.editor.takeSnapshot();
32158
+ }
32159
+ this.editor.formatContentModel(function (model) {
32160
+ model.blocks = modelBeforePaste_1.blocks;
32161
+ return true;
32162
+ }, {
32163
+ apiName: 'MarkdownConversion',
32164
+ scrollCaretIntoView: true,
32165
+ });
32166
+ }
32167
+ }
32168
+ else if (event.eventType === 'beforePaste' && !event.clipboardData.pasteNativeEvent) {
32169
+ var shouldConvert = event.pasteType === 'asMarkdown';
32170
+ if (shouldConvert && (0, isPastedContentMarkdown_1.isPastedContentMarkdown)(this.editor, event.clipboardData)) {
32171
+ convertPastedTextToMarkdown(this.editor, event.fragment, event.clipboardData.text);
32172
+ }
32173
+ }
32174
+ };
32175
+ return MarkdownPastePlugin;
32176
+ }());
32177
+ exports.MarkdownPastePlugin = MarkdownPastePlugin;
32178
+ function convertPastedTextToMarkdown(editor, fragment, text) {
32179
+ var model = (0, convertMarkdownToContentModel_1.convertMarkdownToContentModel)(text, {
32180
+ emptyLine: 'merge',
32181
+ });
32182
+ while (fragment.firstChild) {
32183
+ fragment.removeChild(fragment.firstChild);
32184
+ }
32185
+ (0, roosterjs_content_model_dom_1.contentModelToDom)(editor.getDocument(), fragment, model, (0, roosterjs_content_model_dom_1.createModelToDomContext)());
32186
+ }
32187
+
32188
+
32189
+ /***/ },
32190
+
32191
+ /***/ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts"
32192
+ /*!**************************************************************************************!*\
32193
+ !*** ./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts ***!
32194
+ \**************************************************************************************/
32195
+ (__unused_webpack_module, exports, __webpack_require__) {
32196
+
32197
+ "use strict";
32198
+
32199
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32200
+ exports.isContentMarkdown = void 0;
32201
+ var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
32202
+ // Block-level markdown patterns. A line that matches any of these is considered markdown.
32203
+ var BlockPatterns = [
32204
+ /^#{1,6}\s.+/,
32205
+ /^\s*>\s.+/,
32206
+ /^\s*[\*\-\+]\s.+/,
32207
+ /^\s*\d+\.\s.+/,
32208
+ /^---+$/,
32209
+ /^\s*\|.*\|\s*$/, // table row: "| a | b |"
32210
+ ];
32211
+ // Inline markdown patterns. The text contains markdown if any of these match anywhere.
32212
+ var InlinePatterns = [
32213
+ /!\[[^\[\]]+\]\([^\)\s]+\)/,
32214
+ /\[[^\[\]]+\]\([^\)\s]+\)/,
32215
+ /\*\*[^\s*][^*]*\*\*/,
32216
+ /(^|[^*])\*[^\s*][^*]*\*([^*]|$)/,
32217
+ /~~[^\s~][^~]*~~/, // strikethrough: ~~text~~
32218
+ ];
32219
+ /**
32220
+ * Detect whether the given plain text contains any markdown markup.
32221
+ * Recognizes block-level patterns (headings, blockquotes, lists, horizontal rules, tables)
32222
+ * and inline patterns (bold, italic, strikethrough, links, images).
32223
+ * @param text The plain text to check.
32224
+ * @returns True if the text contains any markdown markup, false otherwise.
32225
+ */
32226
+ function isContentMarkdown(text) {
32227
+ var e_1, _a, e_2, _b, e_3, _c;
32228
+ if (!text || !text.trim()) {
32229
+ return false;
32230
+ }
32231
+ var lines = text.split(/\r\n|\r|\n/);
32232
+ try {
32233
+ for (var lines_1 = (0, tslib_1.__values)(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
32234
+ var line = lines_1_1.value;
32235
+ try {
32236
+ for (var BlockPatterns_1 = (e_2 = void 0, (0, tslib_1.__values)(BlockPatterns)), BlockPatterns_1_1 = BlockPatterns_1.next(); !BlockPatterns_1_1.done; BlockPatterns_1_1 = BlockPatterns_1.next()) {
32237
+ var pattern = BlockPatterns_1_1.value;
32238
+ if (pattern.test(line)) {
32239
+ return true;
32240
+ }
32241
+ }
32242
+ }
32243
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
32244
+ finally {
32245
+ try {
32246
+ if (BlockPatterns_1_1 && !BlockPatterns_1_1.done && (_b = BlockPatterns_1.return)) _b.call(BlockPatterns_1);
32247
+ }
32248
+ finally { if (e_2) throw e_2.error; }
32249
+ }
32250
+ }
32251
+ }
32252
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
32253
+ finally {
32254
+ try {
32255
+ if (lines_1_1 && !lines_1_1.done && (_a = lines_1.return)) _a.call(lines_1);
32256
+ }
32257
+ finally { if (e_1) throw e_1.error; }
32258
+ }
32259
+ try {
32260
+ for (var InlinePatterns_1 = (0, tslib_1.__values)(InlinePatterns), InlinePatterns_1_1 = InlinePatterns_1.next(); !InlinePatterns_1_1.done; InlinePatterns_1_1 = InlinePatterns_1.next()) {
32261
+ var pattern = InlinePatterns_1_1.value;
32262
+ if (pattern.test(text)) {
32263
+ return true;
32264
+ }
32265
+ }
32266
+ }
32267
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
32268
+ finally {
32269
+ try {
32270
+ if (InlinePatterns_1_1 && !InlinePatterns_1_1.done && (_c = InlinePatterns_1.return)) _c.call(InlinePatterns_1);
32271
+ }
32272
+ finally { if (e_3) throw e_3.error; }
32273
+ }
32274
+ return false;
32275
+ }
32276
+ exports.isContentMarkdown = isContentMarkdown;
32277
+
32278
+
32279
+ /***/ },
32280
+
32281
+ /***/ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts"
32282
+ /*!********************************************************************************************!*\
32283
+ !*** ./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts ***!
32284
+ \********************************************************************************************/
32285
+ (__unused_webpack_module, exports, __webpack_require__) {
32286
+
32287
+ "use strict";
32288
+
32289
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32290
+ exports.isPastedContentMarkdown = void 0;
32291
+ var isContentMarkdown_1 = __webpack_require__(/*! ./isContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts");
32292
+ // Tags that are considered "thin wrappers", which only add structure (such as line breaks)
32293
+ // around the plain text without applying any real formatting to its content.
32294
+ var ThinWrapperTags = new Set(['DIV', 'P', 'BR', 'SPAN']);
32295
+ var AllowedAttributes = new Set(['class', 'style']);
32296
+ /**
32297
+ * Detect whether the given clipboard content can be interpreted as markdown.
32298
+ * @param editor The editor instance.
32299
+ * @param clipboardData The clipboard data to check.
32300
+ * @returns True if the content can be interpreted as markdown, false otherwise.
32301
+ */
32302
+ function isPastedContentMarkdown(editor, clipboardData) {
32303
+ var text = clipboardData.text, rawHtml = clipboardData.rawHtml;
32304
+ if (!text || !text.trim()) {
32305
+ return false;
32306
+ }
32307
+ if ((0, isContentMarkdown_1.isContentMarkdown)(text)) {
32308
+ if (!rawHtml) {
32309
+ return true;
32310
+ }
32311
+ var doc = editor.getDocument();
32312
+ var trustedHTMLHandler = editor.getDOMCreator();
32313
+ var fragment = parseHtmlToFragment(rawHtml, doc, trustedHTMLHandler);
32314
+ return isThinWrapperOfPlainText(fragment, text);
32315
+ }
32316
+ return false;
32317
+ }
32318
+ exports.isPastedContentMarkdown = isPastedContentMarkdown;
32319
+ function isThinWrapperOfPlainText(fragment, text) {
32320
+ var elements = fragment.querySelectorAll('*');
32321
+ for (var i = 0; i < elements.length; i++) {
32322
+ var element = elements[i];
32323
+ if (!ThinWrapperTags.has(element.tagName)) {
32324
+ return false;
32325
+ }
32326
+ for (var j = 0; j < element.attributes.length; j++) {
32327
+ var attr = element.attributes[j];
32328
+ if (!AllowedAttributes.has(attr.name) && !attr.name.startsWith('data-')) {
32329
+ return false;
32330
+ }
32331
+ }
32332
+ }
32333
+ return removeWhitespace(fragment.textContent || '') === removeWhitespace(text);
32334
+ }
32335
+ function removeWhitespace(text) {
32336
+ return text.replace(/\s/g, '');
32337
+ }
32338
+ function parseHtmlToFragment(html, doc, trustedHTMLHandler) {
32339
+ var parsedDoc = trustedHTMLHandler.htmlToDOM(html);
32340
+ var fragment = doc.createDocumentFragment();
32341
+ var body = parsedDoc === null || parsedDoc === void 0 ? void 0 : parsedDoc.body;
32342
+ if (body) {
32343
+ while (body.firstChild) {
32344
+ fragment.appendChild(body.firstChild);
32345
+ }
32346
+ }
32347
+ return fragment;
32348
+ }
32349
+
32350
+
31905
32351
  /***/ },
31906
32352
 
31907
32353
  /***/ "./packages/roosterjs-content-model-plugins/lib/announce/AnnouncePlugin.ts"
@@ -37666,16 +38112,31 @@ function createImageWrapper(editor, image, options, editInfo, htmlOptions, opera
37666
38112
  croppers = (0, createImageCropper_1.createImageCropper)(doc);
37667
38113
  }
37668
38114
  var wrapper = createWrapper(editor, imageClone, options, editInfo, resizers, rotators, croppers);
38115
+ // Capture the image footprint before attaching the shadow root, since the light-DOM image
38116
+ // stops being rendered once the shadow root takes over.
38117
+ var imageWidth = image.offsetWidth;
38118
+ var imageHeight = image.offsetHeight;
37669
38119
  var imageSpan = (0, roosterjs_content_model_dom_1.wrap)(doc, image, 'span');
37670
- var shadowSpan = createShadowSpan(wrapper, imageSpan);
38120
+ var shadowSpan = createShadowSpan(wrapper, imageSpan, imageWidth, imageHeight);
37671
38121
  return { wrapper: wrapper, shadowSpan: shadowSpan, imageClone: imageClone, resizers: resizers, rotators: rotators, croppers: croppers };
37672
38122
  }
37673
38123
  exports.createImageWrapper = createImageWrapper;
37674
- var createShadowSpan = function (wrapper, imageSpan) {
38124
+ var createShadowSpan = function (wrapper, imageSpan, imageWidth, imageHeight) {
37675
38125
  var shadowRoot = imageSpan.attachShadow({
37676
38126
  mode: 'open',
37677
38127
  });
37678
38128
  imageSpan.id = IMAGE_EDIT_SHADOW_ROOT;
38129
+ // Pin the shadow host to the original image's box so that wrapping the image does not grow the
38130
+ // surrounding line box. Without this, the taller edit wrapper enlarges the line and the browser
38131
+ // scrolls the selection back into view, making the editor jump. The wrapper and handles still
38132
+ // overflow the host visually because overflow is left visible.
38133
+ if (imageWidth > 0 && imageHeight > 0) {
38134
+ imageSpan.style.display = 'inline-block';
38135
+ imageSpan.style.width = imageWidth + "px";
38136
+ imageSpan.style.height = imageHeight + "px";
38137
+ imageSpan.style.verticalAlign = 'bottom';
38138
+ imageSpan.style.overflow = 'visible';
38139
+ }
37679
38140
  shadowRoot.appendChild(wrapper);
37680
38141
  return imageSpan;
37681
38142
  };