roosterjs 9.53.0 → 9.54.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.
@@ -1,4 +1,4 @@
1
- // Type definitions for roosterjs (Version 9.53.0)
1
+ // Type definitions for roosterjs (Version 9.54.0)
2
2
  // Generated by dts tool from roosterjs
3
3
  // Project: https://github.com/Microsoft/roosterjs
4
4
 
@@ -1511,7 +1511,11 @@ export type PasteType = /**
1511
1511
  /**
1512
1512
  * If there is a image uri in the clipboard, paste the content as image element
1513
1513
  */
1514
- | 'asImage';
1514
+ | 'asImage'
1515
+ /**
1516
+ * If the editor includes a markdown plugin @see MarkdownPastePlugin, and there is markdown content in the clipboard, paste it as markdown
1517
+ */
1518
+ | 'asMarkdown';
1515
1519
 
1516
1520
  /**
1517
1521
  * All Border operations
@@ -2883,6 +2887,13 @@ export interface DomToModelSettings {
2883
2887
  * If true elements that has display:none style will be processed
2884
2888
  */
2885
2889
  processNonVisibleElements?: boolean;
2890
+ /**
2891
+ * When set to true, if a container element could be represented by a FormatContainer, always keep the
2892
+ * FormatContainer and never fall back to a paragraph, even when it only has a single child.
2893
+ * Set this when the intermediate FormatContainer is persisted during DOM to Content Model conversion
2894
+ * and is later used during formatting.
2895
+ */
2896
+ skipFormatContainerFallbackCheck?: boolean;
2886
2897
  }
2887
2898
 
2888
2899
  /**
@@ -3303,6 +3314,13 @@ export interface DomToModelOptionForCreateModel extends DomToModelOption {
3303
3314
  * When this option is passed, "tryGetFromCache" will be ignored.
3304
3315
  */
3305
3316
  recalculateTableSize?: boolean | 'all' | 'selected' | 'none';
3317
+ /**
3318
+ * When set to true, if a container element could be represented by a FormatContainer, always keep the
3319
+ * FormatContainer and never fall back to a paragraph, even when it only has a single child.
3320
+ * Set this when the intermediate FormatContainer is persisted during DOM to Content Model conversion
3321
+ * and is later used during formatting.
3322
+ */
3323
+ skipFormatContainerFallbackCheck?: boolean;
3306
3324
  }
3307
3325
 
3308
3326
  /**
@@ -3412,6 +3430,14 @@ export interface DomIndexer {
3412
3430
  * @returns True if successfully updated, otherwise false
3413
3431
  */
3414
3432
  reconcileElementId: (element: HTMLElement) => boolean;
3433
+ /**
3434
+ * When src or a data-* attribute is changed on an indexed IMG element, update the related
3435
+ * ContentModelImage (src property or dataset) so the cached model stays valid.
3436
+ * @param element The element that has an attribute changed
3437
+ * @param attributeName The name of the changed attribute
3438
+ * @returns True if successfully updated, otherwise false
3439
+ */
3440
+ reconcileImageAttribute: (element: HTMLElement, attributeName: string) => boolean;
3415
3441
  /**
3416
3442
  * When child list of editor content is changed, we can use this method to do sync the change from editor into content model.
3417
3443
  * This is mostly used when user start to type in an empty line. In that case browser will remove the existing BR node in the empty line if any,
@@ -6335,7 +6361,7 @@ export interface BeforePasteEvent extends BasePluginEvent<'beforePaste'> {
6335
6361
  */
6336
6362
  readonly htmlAttributes: Record<string, string>;
6337
6363
  /**
6338
- * Paste type option (as plain text, merge format, normal, as image)
6364
+ * Paste type option (as plain text, merge format, normal, as image, asMarkdown (@see MarkdownPastePlugin ))
6339
6365
  */
6340
6366
  readonly pasteType: PasteType;
6341
6367
  /**
@@ -7667,7 +7693,7 @@ export function createDomToModelContext(editorContext?: EditorContext, ...option
7667
7693
  * @param config A full config object to define how to convert DOM tree to Content Model
7668
7694
  * @param editorContext Context of editor
7669
7695
  */
7670
- export function createDomToModelContextWithConfig(config: DomToModelSettings, editorContext?: EditorContext): any;
7696
+ export function createDomToModelContextWithConfig(config: DomToModelSettings, editorContext?: EditorContext): DomToModelContext;
7671
7697
 
7672
7698
  /**
7673
7699
  * Create Dom to Content Model Config object
@@ -10918,6 +10944,23 @@ export function convertMarkdownToContentModel(text: string, options?: MarkdownTo
10918
10944
  */
10919
10945
  export function convertContentModelToMarkdown(model: ContentModelDocument, newLine?: MarkdownLineBreaks): string;
10920
10946
 
10947
+ /**
10948
+ * Detect whether the given plain text contains any markdown markup.
10949
+ * Recognizes block-level patterns (headings, blockquotes, lists, horizontal rules, tables)
10950
+ * and inline patterns (bold, italic, strikethrough, links, images).
10951
+ * @param text The plain text to check.
10952
+ * @returns True if the text contains any markdown markup, false otherwise.
10953
+ */
10954
+ export function isContentMarkdown(text: string): boolean;
10955
+
10956
+ /**
10957
+ * Detect whether the given clipboard content can be interpreted as markdown.
10958
+ * @param editor The editor instance.
10959
+ * @param clipboardData The clipboard data to check.
10960
+ * @returns True if the content can be interpreted as markdown, false otherwise.
10961
+ */
10962
+ export function isPastedContentMarkdown(editor: IEditor, clipboardData: ClipboardData): boolean;
10963
+
10921
10964
  /**
10922
10965
  * The characters to add line breaks and new lines
10923
10966
  */
@@ -10949,3 +10992,55 @@ export interface MarkdownToModelOptions {
10949
10992
  direction?: 'ltr' | 'rtl' | undefined;
10950
10993
  }
10951
10994
 
10995
+ /**
10996
+ * Markdown paste plugin. Handles the BeforePaste event and, when the pasted content
10997
+ * can be interpreted as markdown, converts the plain text into a Content Model and
10998
+ * pastes it as rich markdown content instead of the original clipboard HTML.
10999
+ */
11000
+ export class MarkdownPastePlugin implements EditorPlugin {
11001
+ private editor;
11002
+ private options;
11003
+ /**
11004
+ * Construct a new instance of MarkdownPastePlugin
11005
+ * @param options Options to control the markdown paste behavior
11006
+ */
11007
+ constructor(options?: MarkdownPasteOptions);
11008
+ /**
11009
+ * Get name of this plugin
11010
+ */
11011
+ getName(): string;
11012
+ /**
11013
+ * The first method that editor will call to a plugin when editor is initializing.
11014
+ * It will pass in the editor instance, plugin should take this chance to save the
11015
+ * editor reference so that it can call to any editor method or format API later.
11016
+ * @param editor The editor object
11017
+ */
11018
+ initialize(editor: IEditor): void;
11019
+ /**
11020
+ * The last method that editor will call to a plugin before it is disposed.
11021
+ * Plugin can take this chance to clear the reference to editor. After this method is
11022
+ * called, plugin should not call to any editor method since it will result in error.
11023
+ */
11024
+ dispose(): void;
11025
+ /**
11026
+ * Core method for a plugin. Once an event happens in editor, editor will call this
11027
+ * method of each plugin to handle the event as long as the event is not handled
11028
+ * exclusively by another plugin.
11029
+ * @param event The event to handle:
11030
+ */
11031
+ onPluginEvent(event: PluginEvent): void;
11032
+ }
11033
+
11034
+ /**
11035
+ * Options for MarkdownPastePlugin
11036
+ */
11037
+ export interface MarkdownPasteOptions {
11038
+ /**
11039
+ * When true, content that can be interpreted as markdown is automatically converted
11040
+ * into rich content on every paste, without requiring an explicit "Paste as Markdown"
11041
+ * command.
11042
+ * @default false
11043
+ */
11044
+ autoConversion: boolean;
11045
+ }
11046
+
@@ -8019,6 +8019,15 @@ function formatInsertPointWithContentModel(editor, insertPoint, callback, option
8019
8019
  textWithSelection: getShadowTextProcessor(bundle),
8020
8020
  },
8021
8021
  tryGetFromCache: false,
8022
+ // When an element carries "container level" styles such as margin or padding, we first
8023
+ // wrap it in a FormatContainer. After all its child nodes are processed, we decide whether
8024
+ // to keep the FormatContainer or fall back to a plain paragraph when it only wraps a single
8025
+ // paragraph. However, formatInsertPointWithContentModel persists the Content Model group path
8026
+ // during processing so the later formatting callback can still use it (see the
8027
+ // DomToModelContextWithPath interface below). If the FormatContainer falls back to a paragraph,
8028
+ // it is removed from the model and the persisted path becomes invalid. To keep the path valid,
8029
+ // we skip the fallback check here and always keep the FormatContainer when one is needed.
8030
+ skipFormatContainerFallbackCheck: true,
8022
8031
  });
8023
8032
  }
8024
8033
  exports.formatInsertPointWithContentModel = formatInsertPointWithContentModel;
@@ -10103,6 +10112,9 @@ var createContentModel = function (core, option, selectionOverride) {
10103
10112
  var domToModelContext = option
10104
10113
  ? (0, roosterjs_content_model_dom_1.createDomToModelContext)(editorContext, settings.builtIn, settings.customized, option)
10105
10114
  : (0, roosterjs_content_model_dom_1.createDomToModelContextWithConfig)(settings.calculated, editorContext);
10115
+ if (option === null || option === void 0 ? void 0 : option.skipFormatContainerFallbackCheck) {
10116
+ domToModelContext.skipFormatContainerFallbackCheck = true;
10117
+ }
10106
10118
  if (selection) {
10107
10119
  domToModelContext.selection = selection;
10108
10120
  }
@@ -11574,6 +11586,11 @@ var CachePlugin = /** @class */ (function () {
11574
11586
  _this.invalidateCache();
11575
11587
  }
11576
11588
  break;
11589
+ case 'attribute':
11590
+ if (!_this.state.domIndexer.reconcileImageAttribute(mutation.element, mutation.attributeName)) {
11591
+ _this.invalidateCache();
11592
+ }
11593
+ break;
11577
11594
  case 'unknown':
11578
11595
  _this.invalidateCache();
11579
11596
  break;
@@ -12105,6 +12122,33 @@ var DomIndexerImpl = /** @class */ (function () {
12105
12122
  return false;
12106
12123
  }
12107
12124
  };
12125
+ DomIndexerImpl.prototype.reconcileImageAttribute = function (element, attributeName) {
12126
+ var _a, _b;
12127
+ if ((0, roosterjs_content_model_dom_1.isElementOfType)(element, 'img')) {
12128
+ var image = (_a = getIndexedSegmentItem(element)) === null || _a === void 0 ? void 0 : _a.segments[0];
12129
+ if ((image === null || image === void 0 ? void 0 : image.segmentType) == 'Image') {
12130
+ if (attributeName == 'src') {
12131
+ // Use getAttribute('src') instead of retrieving src directly, in case the src
12132
+ // has port and may be stripped by browser. This matches imageProcessor.
12133
+ image.src = (_b = element.getAttribute('src')) !== null && _b !== void 0 ? _b : '';
12134
+ return true;
12135
+ }
12136
+ else if (attributeName.indexOf('data-') == 0) {
12137
+ // A data-* attribute may be added, modified or removed. Rebuild the whole
12138
+ // dataset from DOM to keep it in sync, the same way imageProcessor builds it.
12139
+ var dataset_1 = image.dataset;
12140
+ (0, roosterjs_content_model_dom_1.getObjectKeys)(dataset_1).forEach(function (key) {
12141
+ delete dataset_1[key];
12142
+ });
12143
+ (0, roosterjs_content_model_dom_1.getObjectKeys)(element.dataset).forEach(function (key) {
12144
+ dataset_1[key] = element.dataset[key] || '';
12145
+ });
12146
+ return true;
12147
+ }
12148
+ }
12149
+ }
12150
+ return false;
12151
+ };
12108
12152
  DomIndexerImpl.prototype.onBlockEntityDelimiter = function (node, entity, parent) {
12109
12153
  if ((0, roosterjs_content_model_dom_1.isNodeOfType)(node, 'ELEMENT_NODE') && (0, roosterjs_content_model_dom_1.isEntityDelimiter)(node) && node.firstChild) {
12110
12154
  var indexedDelimiter = node.firstChild;
@@ -12420,6 +12464,16 @@ var TextMutationObserverImpl = /** @class */ (function () {
12420
12464
  (0, roosterjs_content_model_dom_1.isNodeOfType)(target, 'ELEMENT_NODE')) {
12421
12465
  _this.onMutation({ type: 'elementId', element: target });
12422
12466
  }
12467
+ else if (mutation.attributeName &&
12468
+ (0, roosterjs_content_model_dom_1.isNodeOfType)(target, 'ELEMENT_NODE') &&
12469
+ (mutation.attributeName == 'src' ||
12470
+ mutation.attributeName.indexOf('data-') == 0)) {
12471
+ _this.onMutation({
12472
+ type: 'attribute',
12473
+ element: target,
12474
+ attributeName: mutation.attributeName,
12475
+ });
12476
+ }
12423
12477
  else {
12424
12478
  // We cannot handle attributes changes on editor content for now
12425
12479
  canHandle = false;
@@ -18136,7 +18190,9 @@ var formatContainerProcessorInternal = function (group, element, context, forceF
18136
18190
  if (element.style.fontSize && parseInt(element.style.fontSize) == 0) {
18137
18191
  formatContainer.zeroFontSize = true;
18138
18192
  }
18139
- if (shouldFallbackToParagraph(formatContainer) && !forceFormatContainer) {
18193
+ if (!context.skipFormatContainerFallbackCheck &&
18194
+ shouldFallbackToParagraph(formatContainer) &&
18195
+ !forceFormatContainer) {
18140
18196
  // For DIV container that only has one paragraph child, container style can be merged into paragraph
18141
18197
  // and no need to have this container
18142
18198
  var paragraph = formatContainer.blocks[0];
@@ -28673,6 +28729,7 @@ function internalIterateSelections(path, callback, option, table, treatAllAsSele
28673
28729
 
28674
28730
  Object.defineProperty(exports, "__esModule", ({ value: true }));
28675
28731
  exports.setSelection = void 0;
28732
+ var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
28676
28733
  var isGeneralSegment_1 = __webpack_require__(/*! ../typeCheck/isGeneralSegment */ "./packages/roosterjs-content-model-dom/lib/modelApi/typeCheck/isGeneralSegment.ts");
28677
28734
  var mutate_1 = __webpack_require__(/*! ../common/mutate */ "./packages/roosterjs-content-model-dom/lib/modelApi/common/mutate.ts");
28678
28735
  /**
@@ -28710,6 +28767,7 @@ function setSelectionToBlockGroup(group, isInSelection, start, end) {
28710
28767
  });
28711
28768
  }
28712
28769
  function setSelectionToBlock(block, isInSelection, start, end) {
28770
+ var _a;
28713
28771
  switch (block.blockType) {
28714
28772
  case 'BlockGroup':
28715
28773
  return setSelectionToBlockGroup(block, isInSelection, start, end);
@@ -28730,16 +28788,31 @@ function setSelectionToBlock(block, isInSelection, start, end) {
28730
28788
  return isInSelection;
28731
28789
  });
28732
28790
  case 'Paragraph':
28733
- var segmentsToDelete_1 = [];
28791
+ var state_1 = {
28792
+ segmentsToDelete: [],
28793
+ boundaryMarkers: [],
28794
+ hasSelectedNonMarker: false,
28795
+ };
28734
28796
  block.segments.forEach(function (segment, i) {
28735
28797
  isInSelection = handleSelection(isInSelection, segment, start, end, function (isInSelection) {
28736
- return setSelectionToSegment(block, segment, isInSelection, segmentsToDelete_1, start, end, i);
28798
+ return setSelectionToSegment(block, segment, isInSelection, state_1, start, end, i);
28737
28799
  });
28738
28800
  });
28739
- if (segmentsToDelete_1.length > 0) {
28801
+ if (state_1.hasSelectedNonMarker) {
28802
+ // This paragraph contains a real (non-marker) selected segment, so any leading/trailing
28803
+ // selection marker of the range is redundant within this paragraph and can be removed.
28804
+ // We only do this within the same paragraph: a boundary marker at a paragraph edge must be
28805
+ // kept to distinguish "selection starts at the beginning of this line" from "selection
28806
+ // starts at the end of the previous line".
28807
+ (_a = state_1.segmentsToDelete).push.apply(_a, (0, tslib_1.__spreadArray)([], (0, tslib_1.__read)(state_1.boundaryMarkers), false));
28808
+ }
28809
+ if (state_1.segmentsToDelete.length > 0) {
28740
28810
  var mutablePara = (0, mutate_1.mutateBlock)(block);
28741
28811
  var index = void 0;
28742
- while ((index = segmentsToDelete_1.pop()) !== undefined) {
28812
+ // Sort ascending so the pop()-based splice below always removes the highest index first,
28813
+ // keeping the remaining indices valid (boundary markers may sit before queued deletions).
28814
+ state_1.segmentsToDelete.sort(function (a, b) { return a - b; });
28815
+ while ((index = state_1.segmentsToDelete.pop()) !== undefined) {
28743
28816
  if (index >= 0) {
28744
28817
  mutablePara.segments.splice(index, 1);
28745
28818
  }
@@ -28788,14 +28861,23 @@ function findCell(table, cell) {
28788
28861
  : -1;
28789
28862
  return { row: row, col: col };
28790
28863
  }
28791
- function setSelectionToSegment(paragraph, segment, isInSelection, segmentsToDelete, start, end, i) {
28864
+ function setSelectionToSegment(paragraph, segment, isInSelection, state, start, end, i) {
28865
+ if (segment.segmentType != 'SelectionMarker' && isInSelection) {
28866
+ state.hasSelectedNonMarker = true;
28867
+ }
28792
28868
  switch (segment.segmentType) {
28793
28869
  case 'SelectionMarker':
28794
28870
  if (!isInSelection || (segment != start && segment != end)) {
28795
28871
  // Delete the selection marker when
28796
28872
  // 1. It is not in selection any more. Or
28797
28873
  // 2. It is in middle of selection, so no need to have it
28798
- segmentsToDelete.push(i);
28874
+ state.segmentsToDelete.push(i);
28875
+ }
28876
+ else {
28877
+ // It is a leading/trailing selection marker of a range selection. Keep it for now, but
28878
+ // remember it so it can be removed later if this same paragraph also contains a real
28879
+ // (non-marker) selected segment, in which case the marker is redundant.
28880
+ state.boundaryMarkers.push(i);
28799
28881
  }
28800
28882
  return isInSelection;
28801
28883
  case 'General':
@@ -29236,7 +29318,7 @@ var handleBlockGroupChildren = function (doc, parent, group, context) {
29236
29318
  (_a = context.domIndexer) === null || _a === void 0 ? void 0 : _a.onBlockEntity(childBlock, group);
29237
29319
  }
29238
29320
  });
29239
- cleanUpNodeStack(listFormat.nodeStack, context);
29321
+ cleanUpNodeStack(listFormat.nodeStack, context, parent);
29240
29322
  // Remove all rest node if any since they don't appear in content model
29241
29323
  (0, cleanUpRestNodes_1.cleanUpRestNodes)(refNode, context.rewriteFromModel);
29242
29324
  }
@@ -29245,7 +29327,7 @@ var handleBlockGroupChildren = function (doc, parent, group, context) {
29245
29327
  }
29246
29328
  };
29247
29329
  exports.handleBlockGroupChildren = handleBlockGroupChildren;
29248
- function cleanUpNodeStack(nodeStack, context) {
29330
+ function cleanUpNodeStack(nodeStack, context, leavingParent) {
29249
29331
  var _a, _b;
29250
29332
  if (nodeStack.length > 0) {
29251
29333
  // Clear list stack, only run to nodeStack[1] because nodeStack[0] is the parent node
@@ -29253,6 +29335,13 @@ function cleanUpNodeStack(nodeStack, context) {
29253
29335
  var node = (_b = (_a = nodeStack.pop()) === null || _a === void 0 ? void 0 : _a.refNode) !== null && _b !== void 0 ? _b : null;
29254
29336
  (0, cleanUpRestNodes_1.cleanUpRestNodes)(node, context.rewriteFromModel);
29255
29337
  }
29338
+ if (leavingParent && nodeStack[0].node == leavingParent) {
29339
+ // When leaving a parent node that is the same with the root of node stack
29340
+ // It means the whole list node stack is being invalidated, so we clear it
29341
+ while (nodeStack.length > 0) {
29342
+ nodeStack.pop();
29343
+ }
29344
+ }
29256
29345
  }
29257
29346
  }
29258
29347
 
@@ -30641,11 +30730,17 @@ exports.MarkdownHeadings = {
30641
30730
  "use strict";
30642
30731
 
30643
30732
  Object.defineProperty(exports, "__esModule", ({ value: true }));
30644
- exports.convertContentModelToMarkdown = exports.convertMarkdownToContentModel = void 0;
30733
+ exports.MarkdownPastePlugin = exports.isPastedContentMarkdown = exports.isContentMarkdown = exports.convertContentModelToMarkdown = exports.convertMarkdownToContentModel = void 0;
30645
30734
  var convertMarkdownToContentModel_1 = __webpack_require__(/*! ./markdownToModel/convertMarkdownToContentModel */ "./packages/roosterjs-content-model-markdown/lib/markdownToModel/convertMarkdownToContentModel.ts");
30646
30735
  Object.defineProperty(exports, "convertMarkdownToContentModel", ({ enumerable: true, get: function () { return convertMarkdownToContentModel_1.convertMarkdownToContentModel; } }));
30647
30736
  var convertContentModelToMarkdown_1 = __webpack_require__(/*! ./modelToMarkdown/convertContentModelToMarkdown */ "./packages/roosterjs-content-model-markdown/lib/modelToMarkdown/convertContentModelToMarkdown.ts");
30648
30737
  Object.defineProperty(exports, "convertContentModelToMarkdown", ({ enumerable: true, get: function () { return convertContentModelToMarkdown_1.convertContentModelToMarkdown; } }));
30738
+ var isContentMarkdown_1 = __webpack_require__(/*! ./publicApi/isContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts");
30739
+ Object.defineProperty(exports, "isContentMarkdown", ({ enumerable: true, get: function () { return isContentMarkdown_1.isContentMarkdown; } }));
30740
+ var isPastedContentMarkdown_1 = __webpack_require__(/*! ./publicApi/isPastedContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts");
30741
+ Object.defineProperty(exports, "isPastedContentMarkdown", ({ enumerable: true, get: function () { return isPastedContentMarkdown_1.isPastedContentMarkdown; } }));
30742
+ var MarkdownPastePlugin_1 = __webpack_require__(/*! ./plugins/MarkdownPastePlugin */ "./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts");
30743
+ Object.defineProperty(exports, "MarkdownPastePlugin", ({ enumerable: true, get: function () { return MarkdownPastePlugin_1.MarkdownPastePlugin; } }));
30649
30744
 
30650
30745
 
30651
30746
  /***/ },
@@ -31335,6 +31430,13 @@ function parseInlineSegments(text, segments, state, link) {
31335
31430
  };
31336
31431
  while (i < text.length) {
31337
31432
  var remaining = text.substring(i);
31433
+ // Escaped character: a backslash followed by an ASCII punctuation character emits
31434
+ // that character literally (e.g. "\*" -> "*") and is never treated as a marker.
31435
+ if (text[i] === '\\' && i + 1 < text.length && isEscapable(text[i + 1])) {
31436
+ buffer += text[i + 1];
31437
+ i += 2;
31438
+ continue;
31439
+ }
31338
31440
  // Image: ![alt](url)
31339
31441
  var imgMatch = imagePattern.exec(remaining);
31340
31442
  if (imgMatch && isValidUrl(imgMatch[2])) {
@@ -31398,6 +31500,10 @@ function shouldToggleFormatting(text, index, marker, currentState) {
31398
31500
  function isWhitespace(char) {
31399
31501
  return /\s/.test(char);
31400
31502
  }
31503
+ function isEscapable(char) {
31504
+ // Per CommonMark, any ASCII punctuation character may be backslash-escaped.
31505
+ return /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(char);
31506
+ }
31401
31507
  function toggleFormatting(state, type) {
31402
31508
  switch (type) {
31403
31509
  case 'bold':
@@ -31901,6 +32007,252 @@ function modelProcessor(model, newLine) {
31901
32007
  exports.modelProcessor = modelProcessor;
31902
32008
 
31903
32009
 
32010
+ /***/ },
32011
+
32012
+ /***/ "./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts"
32013
+ /*!**************************************************************************************!*\
32014
+ !*** ./packages/roosterjs-content-model-markdown/lib/plugins/MarkdownPastePlugin.ts ***!
32015
+ \**************************************************************************************/
32016
+ (__unused_webpack_module, exports, __webpack_require__) {
32017
+
32018
+ "use strict";
32019
+
32020
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32021
+ exports.MarkdownPastePlugin = void 0;
32022
+ var roosterjs_content_model_dom_1 = __webpack_require__(/*! roosterjs-content-model-dom */ "./packages/roosterjs-content-model-dom/lib/index.ts");
32023
+ var convertMarkdownToContentModel_1 = __webpack_require__(/*! ../markdownToModel/convertMarkdownToContentModel */ "./packages/roosterjs-content-model-markdown/lib/markdownToModel/convertMarkdownToContentModel.ts");
32024
+ var isPastedContentMarkdown_1 = __webpack_require__(/*! ../publicApi/isPastedContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts");
32025
+ var DefaultOptions = {
32026
+ autoConversion: false,
32027
+ };
32028
+ /**
32029
+ * Markdown paste plugin. Handles the BeforePaste event and, when the pasted content
32030
+ * can be interpreted as markdown, converts the plain text into a Content Model and
32031
+ * pastes it as rich markdown content instead of the original clipboard HTML.
32032
+ */
32033
+ var MarkdownPastePlugin = /** @class */ (function () {
32034
+ /**
32035
+ * Construct a new instance of MarkdownPastePlugin
32036
+ * @param options Options to control the markdown paste behavior
32037
+ */
32038
+ function MarkdownPastePlugin(options) {
32039
+ this.editor = null;
32040
+ this.options = options !== null && options !== void 0 ? options : DefaultOptions;
32041
+ }
32042
+ /**
32043
+ * Get name of this plugin
32044
+ */
32045
+ MarkdownPastePlugin.prototype.getName = function () {
32046
+ return 'MarkdownPaste';
32047
+ };
32048
+ /**
32049
+ * The first method that editor will call to a plugin when editor is initializing.
32050
+ * It will pass in the editor instance, plugin should take this chance to save the
32051
+ * editor reference so that it can call to any editor method or format API later.
32052
+ * @param editor The editor object
32053
+ */
32054
+ MarkdownPastePlugin.prototype.initialize = function (editor) {
32055
+ this.editor = editor;
32056
+ };
32057
+ /**
32058
+ * The last method that editor will call to a plugin before it is disposed.
32059
+ * Plugin can take this chance to clear the reference to editor. After this method is
32060
+ * called, plugin should not call to any editor method since it will result in error.
32061
+ */
32062
+ MarkdownPastePlugin.prototype.dispose = function () {
32063
+ this.editor = null;
32064
+ };
32065
+ /**
32066
+ * Core method for a plugin. Once an event happens in editor, editor will call this
32067
+ * method of each plugin to handle the event as long as the event is not handled
32068
+ * exclusively by another plugin.
32069
+ * @param event The event to handle:
32070
+ */
32071
+ MarkdownPastePlugin.prototype.onPluginEvent = function (event) {
32072
+ if (!this.editor || event.eventType != 'beforePaste') {
32073
+ return;
32074
+ }
32075
+ var shouldConvert = event.pasteType === 'asMarkdown' || this.options.autoConversion;
32076
+ if (shouldConvert && (0, isPastedContentMarkdown_1.isPastedContentMarkdown)(this.editor, event.clipboardData)) {
32077
+ convertPastedTextToMarkdown(this.editor, event.fragment, event.clipboardData.text);
32078
+ }
32079
+ };
32080
+ return MarkdownPastePlugin;
32081
+ }());
32082
+ exports.MarkdownPastePlugin = MarkdownPastePlugin;
32083
+ function convertPastedTextToMarkdown(editor, fragment, text) {
32084
+ var model = (0, convertMarkdownToContentModel_1.convertMarkdownToContentModel)(text, {
32085
+ emptyLine: 'merge',
32086
+ });
32087
+ while (fragment.firstChild) {
32088
+ fragment.removeChild(fragment.firstChild);
32089
+ }
32090
+ (0, roosterjs_content_model_dom_1.contentModelToDom)(editor.getDocument(), fragment, model, (0, roosterjs_content_model_dom_1.createModelToDomContext)());
32091
+ }
32092
+
32093
+
32094
+ /***/ },
32095
+
32096
+ /***/ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts"
32097
+ /*!**************************************************************************************!*\
32098
+ !*** ./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts ***!
32099
+ \**************************************************************************************/
32100
+ (__unused_webpack_module, exports, __webpack_require__) {
32101
+
32102
+ "use strict";
32103
+
32104
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32105
+ exports.isContentMarkdown = void 0;
32106
+ var tslib_1 = __webpack_require__(/*! tslib */ "./node_modules/tslib/tslib.es6.mjs");
32107
+ // Block-level markdown patterns. A line that matches any of these is considered markdown.
32108
+ var BlockPatterns = [
32109
+ /^#{1,6}\s.+/,
32110
+ /^\s*>\s.+/,
32111
+ /^\s*[\*\-\+]\s.+/,
32112
+ /^\s*\d+\.\s.+/,
32113
+ /^---+$/,
32114
+ /^\s*\|.*\|\s*$/, // table row: "| a | b |"
32115
+ ];
32116
+ // Inline markdown patterns. The text contains markdown if any of these match anywhere.
32117
+ var InlinePatterns = [
32118
+ /!\[[^\[\]]+\]\([^\)\s]+\)/,
32119
+ /\[[^\[\]]+\]\([^\)\s]+\)/,
32120
+ /\*\*[^\s*][^*]*\*\*/,
32121
+ /(^|[^*])\*[^\s*][^*]*\*([^*]|$)/,
32122
+ /~~[^\s~][^~]*~~/, // strikethrough: ~~text~~
32123
+ ];
32124
+ /**
32125
+ * Detect whether the given plain text contains any markdown markup.
32126
+ * Recognizes block-level patterns (headings, blockquotes, lists, horizontal rules, tables)
32127
+ * and inline patterns (bold, italic, strikethrough, links, images).
32128
+ * @param text The plain text to check.
32129
+ * @returns True if the text contains any markdown markup, false otherwise.
32130
+ */
32131
+ function isContentMarkdown(text) {
32132
+ var e_1, _a, e_2, _b, e_3, _c;
32133
+ if (!text || !text.trim()) {
32134
+ return false;
32135
+ }
32136
+ var lines = text.split(/\r\n|\r|\n/);
32137
+ try {
32138
+ 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()) {
32139
+ var line = lines_1_1.value;
32140
+ try {
32141
+ 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()) {
32142
+ var pattern = BlockPatterns_1_1.value;
32143
+ if (pattern.test(line)) {
32144
+ return true;
32145
+ }
32146
+ }
32147
+ }
32148
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
32149
+ finally {
32150
+ try {
32151
+ if (BlockPatterns_1_1 && !BlockPatterns_1_1.done && (_b = BlockPatterns_1.return)) _b.call(BlockPatterns_1);
32152
+ }
32153
+ finally { if (e_2) throw e_2.error; }
32154
+ }
32155
+ }
32156
+ }
32157
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
32158
+ finally {
32159
+ try {
32160
+ if (lines_1_1 && !lines_1_1.done && (_a = lines_1.return)) _a.call(lines_1);
32161
+ }
32162
+ finally { if (e_1) throw e_1.error; }
32163
+ }
32164
+ try {
32165
+ 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()) {
32166
+ var pattern = InlinePatterns_1_1.value;
32167
+ if (pattern.test(text)) {
32168
+ return true;
32169
+ }
32170
+ }
32171
+ }
32172
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
32173
+ finally {
32174
+ try {
32175
+ if (InlinePatterns_1_1 && !InlinePatterns_1_1.done && (_c = InlinePatterns_1.return)) _c.call(InlinePatterns_1);
32176
+ }
32177
+ finally { if (e_3) throw e_3.error; }
32178
+ }
32179
+ return false;
32180
+ }
32181
+ exports.isContentMarkdown = isContentMarkdown;
32182
+
32183
+
32184
+ /***/ },
32185
+
32186
+ /***/ "./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts"
32187
+ /*!********************************************************************************************!*\
32188
+ !*** ./packages/roosterjs-content-model-markdown/lib/publicApi/isPastedContentMarkdown.ts ***!
32189
+ \********************************************************************************************/
32190
+ (__unused_webpack_module, exports, __webpack_require__) {
32191
+
32192
+ "use strict";
32193
+
32194
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
32195
+ exports.isPastedContentMarkdown = void 0;
32196
+ var isContentMarkdown_1 = __webpack_require__(/*! ./isContentMarkdown */ "./packages/roosterjs-content-model-markdown/lib/publicApi/isContentMarkdown.ts");
32197
+ // Tags that are considered "thin wrappers", which only add structure (such as line breaks)
32198
+ // around the plain text without applying any real formatting to its content.
32199
+ var ThinWrapperTags = new Set(['DIV', 'P', 'BR', 'SPAN']);
32200
+ var AllowedAttributes = new Set(['class', 'style']);
32201
+ /**
32202
+ * Detect whether the given clipboard content can be interpreted as markdown.
32203
+ * @param editor The editor instance.
32204
+ * @param clipboardData The clipboard data to check.
32205
+ * @returns True if the content can be interpreted as markdown, false otherwise.
32206
+ */
32207
+ function isPastedContentMarkdown(editor, clipboardData) {
32208
+ var text = clipboardData.text, rawHtml = clipboardData.rawHtml;
32209
+ if (!text || !text.trim()) {
32210
+ return false;
32211
+ }
32212
+ if ((0, isContentMarkdown_1.isContentMarkdown)(text)) {
32213
+ if (!rawHtml) {
32214
+ return true;
32215
+ }
32216
+ var doc = editor.getDocument();
32217
+ var trustedHTMLHandler = editor.getDOMCreator();
32218
+ var fragment = parseHtmlToFragment(rawHtml, doc, trustedHTMLHandler);
32219
+ return isThinWrapperOfPlainText(fragment, text);
32220
+ }
32221
+ return false;
32222
+ }
32223
+ exports.isPastedContentMarkdown = isPastedContentMarkdown;
32224
+ function isThinWrapperOfPlainText(fragment, text) {
32225
+ var elements = fragment.querySelectorAll('*');
32226
+ for (var i = 0; i < elements.length; i++) {
32227
+ var element = elements[i];
32228
+ if (!ThinWrapperTags.has(element.tagName)) {
32229
+ return false;
32230
+ }
32231
+ for (var j = 0; j < element.attributes.length; j++) {
32232
+ var attr = element.attributes[j];
32233
+ if (!AllowedAttributes.has(attr.name) && !attr.name.startsWith('data-')) {
32234
+ return false;
32235
+ }
32236
+ }
32237
+ }
32238
+ return removeWhitespace(fragment.textContent || '') === removeWhitespace(text);
32239
+ }
32240
+ function removeWhitespace(text) {
32241
+ return text.replace(/\s/g, '');
32242
+ }
32243
+ function parseHtmlToFragment(html, doc, trustedHTMLHandler) {
32244
+ var parsedDoc = trustedHTMLHandler.htmlToDOM(html);
32245
+ var fragment = doc.createDocumentFragment();
32246
+ var body = parsedDoc === null || parsedDoc === void 0 ? void 0 : parsedDoc.body;
32247
+ if (body) {
32248
+ while (body.firstChild) {
32249
+ fragment.appendChild(body.firstChild);
32250
+ }
32251
+ }
32252
+ return fragment;
32253
+ }
32254
+
32255
+
31904
32256
  /***/ },
31905
32257
 
31906
32258
  /***/ "./packages/roosterjs-content-model-plugins/lib/announce/AnnouncePlugin.ts"