roosterjs-content-model-dom 9.11.0 → 9.11.2

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.
@@ -45,8 +45,6 @@ function isSegmentEmpty(segment) {
45
45
  switch (segment.segmentType) {
46
46
  case 'Text':
47
47
  return !segment.text;
48
- case 'Image':
49
- return !segment.src;
50
48
  default:
51
49
  return false;
52
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":";;;AAMA;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAgC;IACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;QACrB,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAEtC,KAAK,OAAO;YACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;QAE1D,KAAK,YAAY;YACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEpC,KAAK,QAAQ;YACT,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAjBD,oCAiBC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,KAAqC;IACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;QAC1B,KAAK,iBAAiB;YAClB,uFAAuF;YACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE7E,KAAK,UAAU;YACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AAjBD,8CAiBC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,OAAoC;IAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;QACzB,KAAK,MAAM;YACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAEzB,KAAK,OAAO;YACR,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAExB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAXD,wCAWC;AAED;;;GAGG;AACH,SAAgB,OAAO,CACnB,KAA+F;IAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;QACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;KACnC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;QACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;KAC9B;SAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;QACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;KAChC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAZD,0BAYC;AAED,SAAS,SAAS,CACd,KAA+F;IAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;AAChF,CAAC;AAED,SAAS,OAAO,CACZ,KAA+F;IAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CACjB,KAA+F;IAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;AACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n case 'Image':\n return !segment.src;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
1
+ {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":";;;AAMA;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAgC;IACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;QACrB,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAEtC,KAAK,OAAO;YACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;QAE1D,KAAK,YAAY;YACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEpC,KAAK,QAAQ;YACT,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAjBD,oCAiBC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,KAAqC;IACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;QAC1B,KAAK,iBAAiB;YAClB,uFAAuF;YACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE7E,KAAK,UAAU;YACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AAjBD,8CAiBC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,OAAoC;IAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;QACzB,KAAK,MAAM;YACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAEzB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AARD,wCAQC;AAED;;;GAGG;AACH,SAAgB,OAAO,CACnB,KAA+F;IAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;QACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;KACnC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;QACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;KAC9B;SAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;QACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;KAChC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAZD,0BAYC;AAED,SAAS,SAAS,CACd,KAA+F;IAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;AAChF,CAAC;AAED,SAAS,OAAO,CACZ,KAA+F;IAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CACjB,KAA+F;IAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;AACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
@@ -29,6 +29,7 @@ function normalizeParagraph(paragraph) {
29
29
  (0, mutate_1.mutateBlock)(paragraph).segments.pop();
30
30
  }
31
31
  }
32
+ normalizeParagraphStyle(paragraph);
32
33
  }
33
34
  if (!(0, isWhiteSpacePreserved_1.isWhiteSpacePreserved)(paragraph.format.whiteSpace)) {
34
35
  (0, normalizeSegment_1.normalizeAllSegments)(paragraph);
@@ -38,6 +39,13 @@ function normalizeParagraph(paragraph) {
38
39
  moveUpSegmentFormat(paragraph);
39
40
  }
40
41
  exports.normalizeParagraph = normalizeParagraph;
42
+ function normalizeParagraphStyle(paragraph) {
43
+ // New paragraph should not have white-space style
44
+ if (paragraph.format.whiteSpace &&
45
+ paragraph.segments.every(function (seg) { return seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'; })) {
46
+ delete (0, mutate_1.mutateBlock)(paragraph).format.whiteSpace;
47
+ }
48
+ }
41
49
  function removeEmptySegments(block) {
42
50
  for (var j = block.segments.length - 1; j >= 0; j--) {
43
51
  if ((0, isEmpty_1.isSegmentEmpty)(block.segments[j])) {
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n\n normalizeParagraphStyle(paragraph);\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction normalizeParagraphStyle(paragraph: ReadonlyContentModelParagraph) {\n // New paragraph should not have white-space style\n if (\n paragraph.format.whiteSpace &&\n paragraph.segments.every(\n seg => seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'\n )\n ) {\n delete mutateBlock(paragraph).format.whiteSpace;\n }\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
@@ -46,8 +46,6 @@ define(["require", "exports"], function (require, exports) {
46
46
  switch (segment.segmentType) {
47
47
  case 'Text':
48
48
  return !segment.text;
49
- case 'Image':
50
- return !segment.src;
51
49
  default:
52
50
  return false;
53
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":";;;;IAMA;;OAEG;IACH,SAAgB,YAAY,CAAC,KAAgC;QACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;YACrB,KAAK,WAAW;gBACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;YAEtC,KAAK,OAAO;gBACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;YAE1D,KAAK,YAAY;gBACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAEpC,KAAK,QAAQ;gBACT,OAAO,KAAK,CAAC;YAEjB;gBACI,OAAO,KAAK,CAAC;SACpB;IACL,CAAC;IAjBD,oCAiBC;IAED;;OAEG;IACH,SAAgB,iBAAiB,CAAC,KAAqC;QACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;YAC1B,KAAK,iBAAiB;gBAClB,uFAAuF;gBACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE7E,KAAK,UAAU;gBACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE5C,KAAK,UAAU,CAAC;YAChB,KAAK,SAAS,CAAC;YACf,KAAK,WAAW;gBACZ,OAAO,KAAK,CAAC;YAEjB;gBACI,OAAO,IAAI,CAAC;SACnB;IACL,CAAC;IAjBD,8CAiBC;IAED;;OAEG;IACH,SAAgB,cAAc,CAAC,OAAoC;QAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;YACzB,KAAK,MAAM;gBACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;YAEzB,KAAK,OAAO;gBACR,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;YAExB;gBACI,OAAO,KAAK,CAAC;SACpB;IACL,CAAC;IAXD,wCAWC;IAED;;;OAGG;IACH,SAAgB,OAAO,CACnB,KAA+F;QAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;YACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;SACnC;aAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;YACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;SAC9B;aAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;YACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;SAChC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAZD,0BAYC;IAED,SAAS,SAAS,CACd,KAA+F;QAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;IAChF,CAAC;IAED,SAAS,OAAO,CACZ,KAA+F;QAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;IAC5E,CAAC;IAED,SAAS,YAAY,CACjB,KAA+F;QAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;IACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n case 'Image':\n return !segment.src;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
1
+ {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":";;;;IAMA;;OAEG;IACH,SAAgB,YAAY,CAAC,KAAgC;QACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;YACrB,KAAK,WAAW;gBACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;YAEtC,KAAK,OAAO;gBACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;YAE1D,KAAK,YAAY;gBACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAEpC,KAAK,QAAQ;gBACT,OAAO,KAAK,CAAC;YAEjB;gBACI,OAAO,KAAK,CAAC;SACpB;IACL,CAAC;IAjBD,oCAiBC;IAED;;OAEG;IACH,SAAgB,iBAAiB,CAAC,KAAqC;QACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;YAC1B,KAAK,iBAAiB;gBAClB,uFAAuF;gBACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE7E,KAAK,UAAU;gBACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE5C,KAAK,UAAU,CAAC;YAChB,KAAK,SAAS,CAAC;YACf,KAAK,WAAW;gBACZ,OAAO,KAAK,CAAC;YAEjB;gBACI,OAAO,IAAI,CAAC;SACnB;IACL,CAAC;IAjBD,8CAiBC;IAED;;OAEG;IACH,SAAgB,cAAc,CAAC,OAAoC;QAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;YACzB,KAAK,MAAM;gBACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;YAEzB;gBACI,OAAO,KAAK,CAAC;SACpB;IACL,CAAC;IARD,wCAQC;IAED;;;OAGG;IACH,SAAgB,OAAO,CACnB,KAA+F;QAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;YACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;SACnC;aAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;YACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;SAC9B;aAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;YACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;SAChC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAZD,0BAYC;IAED,SAAS,SAAS,CACd,KAA+F;QAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;IAChF,CAAC;IAED,SAAS,OAAO,CACZ,KAA+F;QAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;IAC5E,CAAC;IAED,SAAS,YAAY,CACjB,KAA+F;QAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;IACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
@@ -24,6 +24,7 @@ define(["require", "exports", "../../domToModel/utils/areSameFormats", "../creat
24
24
  (0, mutate_1.mutateBlock)(paragraph).segments.pop();
25
25
  }
26
26
  }
27
+ normalizeParagraphStyle(paragraph);
27
28
  }
28
29
  if (!(0, isWhiteSpacePreserved_1.isWhiteSpacePreserved)(paragraph.format.whiteSpace)) {
29
30
  (0, normalizeSegment_1.normalizeAllSegments)(paragraph);
@@ -33,6 +34,13 @@ define(["require", "exports", "../../domToModel/utils/areSameFormats", "../creat
33
34
  moveUpSegmentFormat(paragraph);
34
35
  }
35
36
  exports.normalizeParagraph = normalizeParagraph;
37
+ function normalizeParagraphStyle(paragraph) {
38
+ // New paragraph should not have white-space style
39
+ if (paragraph.format.whiteSpace &&
40
+ paragraph.segments.every(function (seg) { return seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'; })) {
41
+ delete (0, mutate_1.mutateBlock)(paragraph).format.whiteSpace;
42
+ }
43
+ }
36
44
  function removeEmptySegments(block) {
37
45
  for (var j = block.segments.length - 1; j >= 0; j--) {
38
46
  if ((0, isEmpty_1.isSegmentEmpty)(block.segments[j])) {
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n\n normalizeParagraphStyle(paragraph);\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction normalizeParagraphStyle(paragraph: ReadonlyContentModelParagraph) {\n // New paragraph should not have white-space style\n if (\n paragraph.format.whiteSpace &&\n paragraph.segments.every(\n seg => seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'\n )\n ) {\n delete mutateBlock(paragraph).format.whiteSpace;\n }\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
@@ -40,8 +40,6 @@ export function isSegmentEmpty(segment) {
40
40
  switch (segment.segmentType) {
41
41
  case 'Text':
42
42
  return !segment.text;
43
- case 'Image':
44
- return !segment.src;
45
43
  default:
46
44
  return false;
47
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgC;IACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;QACrB,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAEtC,KAAK,OAAO;YACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;QAE1D,KAAK,YAAY;YACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEpC,KAAK,QAAQ;YACT,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAqC;IACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;QAC1B,KAAK,iBAAiB;YAClB,uFAAuF;YACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE7E,KAAK,UAAU;YACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoC;IAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;QACzB,KAAK,MAAM;YACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAEzB,KAAK,OAAO;YACR,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;QAExB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CACnB,KAA+F;IAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;QACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;KACnC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;QACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;KAC9B;SAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;QACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;KAChC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CACd,KAA+F;IAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;AAChF,CAAC;AAED,SAAS,OAAO,CACZ,KAA+F;IAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CACjB,KAA+F;IAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;AACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n case 'Image':\n return !segment.src;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
1
+ {"version":3,"file":"isEmpty.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/isEmpty.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgC;IACzD,QAAQ,KAAK,CAAC,SAAS,EAAE;QACrB,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAEtC,KAAK,OAAO;YACR,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAA,GAAG,IAAI,OAAA,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAArB,CAAqB,CAAC,CAAC;QAE1D,KAAK,YAAY;YACb,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEpC,KAAK,QAAQ;YACT,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAqC;IACnE,QAAQ,KAAK,CAAC,cAAc,EAAE;QAC1B,KAAK,iBAAiB;YAClB,uFAAuF;YACvF,OAAO,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE7E,KAAK,UAAU;YACX,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW;YACZ,OAAO,KAAK,CAAC;QAEjB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAoC;IAC/D,QAAQ,OAAO,CAAC,WAAW,EAAE;QACzB,KAAK,MAAM;YACP,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QAEzB;YACI,OAAO,KAAK,CAAC;KACpB;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CACnB,KAA+F;IAE/F,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE;QACrB,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;KACnC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE;QACvB,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;KAC9B;SAAM,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE;QACzB,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;KAChC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CACd,KAA+F;IAE/F,OAAO,OAAqC,KAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;AAChF,CAAC;AAED,SAAS,OAAO,CACZ,KAA+F;IAE/F,OAAO,OAAmC,KAAM,CAAC,SAAS,KAAK,QAAQ,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CACjB,KAA+F;IAE/F,OAAO,OAAwC,KAAM,CAAC,cAAc,KAAK,QAAQ,CAAC;AACtF,CAAC","sourcesContent":["import type {\n ReadonlyContentModelBlock,\n ReadonlyContentModelBlockGroup,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @internal\n */\nexport function isBlockEmpty(block: ReadonlyContentModelBlock): boolean {\n switch (block.blockType) {\n case 'Paragraph':\n return block.segments.length == 0;\n\n case 'Table':\n return block.rows.every(row => row.cells.length == 0);\n\n case 'BlockGroup':\n return isBlockGroupEmpty(block);\n\n case 'Entity':\n return false;\n\n default:\n return false;\n }\n}\n\n/**\n * @internal\n */\nexport function isBlockGroupEmpty(group: ReadonlyContentModelBlockGroup): boolean {\n switch (group.blockGroupType) {\n case 'FormatContainer':\n // Format Container of DIV is a container for style, so we always treat it as not empty\n return group.tagName == 'div' ? false : group.blocks.every(isBlockEmpty);\n\n case 'ListItem':\n return group.blocks.every(isBlockEmpty);\n\n case 'Document':\n case 'General':\n case 'TableCell':\n return false;\n\n default:\n return true;\n }\n}\n\n/**\n * @internal\n */\nexport function isSegmentEmpty(segment: ReadonlyContentModelSegment): boolean {\n switch (segment.segmentType) {\n case 'Text':\n return !segment.text;\n\n default:\n return false;\n }\n}\n\n/**\n * Get whether the model is empty.\n * @returns true if the model is empty.\n */\nexport function isEmpty(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): boolean {\n if (isBlockGroup(model)) {\n return isBlockGroupEmpty(model);\n } else if (isBlock(model)) {\n return isBlockEmpty(model);\n } else if (isSegment(model)) {\n return isSegmentEmpty(model);\n }\n\n return false;\n}\n\nfunction isSegment(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelSegment {\n return typeof (<ReadonlyContentModelSegment>model).segmentType === 'string';\n}\n\nfunction isBlock(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlock {\n return typeof (<ReadonlyContentModelBlock>model).blockType === 'string';\n}\n\nfunction isBlockGroup(\n model: ReadonlyContentModelBlock | ReadonlyContentModelBlockGroup | ReadonlyContentModelSegment\n): model is ReadonlyContentModelBlockGroup {\n return typeof (<ReadonlyContentModelBlockGroup>model).blockGroupType === 'string';\n}\n"]}
@@ -26,6 +26,7 @@ export function normalizeParagraph(paragraph) {
26
26
  mutateBlock(paragraph).segments.pop();
27
27
  }
28
28
  }
29
+ normalizeParagraphStyle(paragraph);
29
30
  }
30
31
  if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {
31
32
  normalizeAllSegments(paragraph);
@@ -34,6 +35,13 @@ export function normalizeParagraph(paragraph) {
34
35
  removeEmptySegments(paragraph);
35
36
  moveUpSegmentFormat(paragraph);
36
37
  }
38
+ function normalizeParagraphStyle(paragraph) {
39
+ // New paragraph should not have white-space style
40
+ if (paragraph.format.whiteSpace &&
41
+ paragraph.segments.every(function (seg) { return seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'; })) {
42
+ delete mutateBlock(paragraph).format.whiteSpace;
43
+ }
44
+ }
37
45
  function removeEmptySegments(block) {
38
46
  for (var j = block.segments.length - 1; j >= 0; j--) {
39
47
  if (isSegmentEmpty(block.segments[j])) {
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"normalizeParagraph.js","sourceRoot":"","sources":["../../../../../packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts"],"names":[],"mappings":"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","sourcesContent":["import { areSameFormats } from '../../domToModel/utils/areSameFormats';\nimport { createBr } from '../creators/createBr';\nimport { isSegmentEmpty } from './isEmpty';\nimport { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved';\nimport { mutateBlock, mutateSegment } from './mutate';\nimport { normalizeAllSegments } from './normalizeSegment';\nimport type {\n ContentModelSegmentFormat,\n ReadonlyContentModelParagraph,\n ReadonlyContentModelSegment,\n} from 'roosterjs-content-model-types';\n\n/**\n * @param paragraph The paragraph to normalize\n * Normalize a paragraph. If it is empty, add a BR segment to make sure it can insert content\n */\nexport function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) {\n const segments = paragraph.segments;\n\n if (!paragraph.isImplicit && segments.length > 0) {\n const last = segments[segments.length - 1];\n const secondLast = segments[segments.length - 2];\n\n if (\n last.segmentType == 'SelectionMarker' &&\n (!secondLast || secondLast.segmentType == 'Br')\n ) {\n mutateBlock(paragraph).segments.push(createBr(last.format));\n } else if (segments.length > 1 && segments[segments.length - 1].segmentType == 'Br') {\n const noMarkerSegments = segments.filter(x => x.segmentType != 'SelectionMarker');\n\n // When there is content with a <BR> tag at the end, we can remove the BR.\n // But if there are more than one <BR> at the end, do not remove them.\n if (\n noMarkerSegments.length > 1 &&\n noMarkerSegments[noMarkerSegments.length - 2].segmentType != 'Br'\n ) {\n mutateBlock(paragraph).segments.pop();\n }\n }\n\n normalizeParagraphStyle(paragraph);\n }\n\n if (!isWhiteSpacePreserved(paragraph.format.whiteSpace)) {\n normalizeAllSegments(paragraph);\n }\n\n removeEmptyLinks(paragraph);\n\n removeEmptySegments(paragraph);\n\n moveUpSegmentFormat(paragraph);\n}\n\nfunction normalizeParagraphStyle(paragraph: ReadonlyContentModelParagraph) {\n // New paragraph should not have white-space style\n if (\n paragraph.format.whiteSpace &&\n paragraph.segments.every(\n seg => seg.segmentType == 'Br' || seg.segmentType == 'SelectionMarker'\n )\n ) {\n delete mutateBlock(paragraph).format.whiteSpace;\n }\n}\n\nfunction removeEmptySegments(block: ReadonlyContentModelParagraph) {\n for (let j = block.segments.length - 1; j >= 0; j--) {\n if (isSegmentEmpty(block.segments[j])) {\n mutateBlock(block).segments.splice(j, 1);\n }\n }\n}\n\nfunction removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) {\n const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker');\n if (marker) {\n const markerIndex = paragraph.segments.indexOf(marker);\n const prev = paragraph.segments[markerIndex - 1];\n const next = paragraph.segments[markerIndex + 1];\n\n if (\n (prev &&\n !prev.link &&\n areSameFormats(prev.format, marker.format) &&\n (!next || (!next.link && areSameFormats(next.format, marker.format))) &&\n marker.link) ||\n (!prev &&\n marker.link &&\n next &&\n !next.link &&\n areSameFormats(next.format, marker.format))\n ) {\n mutateSegment(paragraph, marker, mutableMarker => {\n delete mutableMarker.link;\n });\n }\n }\n}\n\ntype FormatsToMoveUp = 'fontFamily' | 'fontSize' | 'textColor';\nconst formatsToMoveUp: FormatsToMoveUp[] = ['fontFamily', 'fontSize', 'textColor'];\n\n// When all segments are sharing the same segment format (font name, size and color), we can move its format to paragraph\nfunction moveUpSegmentFormat(paragraph: ReadonlyContentModelParagraph) {\n if (!paragraph.decorator) {\n const segments = paragraph.segments.filter(x => x.segmentType != 'SelectionMarker');\n const target = paragraph.segmentFormat || {};\n let changed = false;\n\n formatsToMoveUp.forEach(key => {\n changed = internalMoveUpSegmentFormat(segments, target, key) || changed;\n });\n\n if (changed) {\n mutateBlock(paragraph).segmentFormat = target;\n }\n }\n}\n\nfunction internalMoveUpSegmentFormat(\n segments: ReadonlyContentModelSegment[],\n target: ContentModelSegmentFormat,\n formatKey: FormatsToMoveUp\n): boolean {\n const firstFormat = segments[0]?.format;\n\n if (\n firstFormat?.[formatKey] &&\n segments.every(segment => segment.format[formatKey] == firstFormat[formatKey]) &&\n target[formatKey] != firstFormat[formatKey]\n ) {\n target[formatKey] = firstFormat[formatKey];\n return true;\n } else {\n return false;\n }\n}\n"]}
package/package.json CHANGED
@@ -3,9 +3,9 @@
3
3
  "description": "Content Model for roosterjs",
4
4
  "dependencies": {
5
5
  "tslib": "^2.3.1",
6
- "roosterjs-content-model-types": "^9.11.0"
6
+ "roosterjs-content-model-types": "^9.11.2"
7
7
  },
8
- "version": "9.11.0",
8
+ "version": "9.11.2",
9
9
  "main": "./lib/index.js",
10
10
  "typings": "./lib/index.d.ts",
11
11
  "module": "./lib-mjs/index.js",