Skip to content

Commit 5d48fd9

Browse files
ianeli1juliaroldiJiuqingSongBryanValverdeUAndres-CT98
authored
Bump content model packages (#2312)
* change image * Content Model: Better hide cursor for table and image selection (#2270) * Standalone Editor: CreateStandaloneEditorCore * Standalone Editor: Port LifecyclePlugin * fix build * fix test * improve * fix test * Standalone Editor: Support keyboard input (init step) * Standalone Editor: Port EntityPlugin * improve * Add test * improve * port selection api * improve * improve * fix build * fix build * fix build * improve * Improve * improve * improve * fix test * improve * add test * remove unused code * Standalone Editor: port ImageSelection plugin * add test * Standalone Editor: Port UndoPlugin * improve * Port undo api * fix test * improve * improve * fix build * Improve * Improve * Improve * fix build * Improve * Add test * fix test * Add undo/redo API * Standalone Editor: Port event core API * fix build * fix build * Standalone Editor: Port transformColor API * Improve * Content Model: Better hide cursor for table and image selection * fix build --------- Co-authored-by: Bryan Valverde U <bvalverde@microsoft.com> * gif treatment * Move Paste from publicApi to coreApi to leverage the same domToModelOptions #2275 * selection * WIP * fix mac image selection * constant * Standalone Editor: Port paste API step 1 (#2279) * Standalone Editor: Port paste API step 2 (#2280) * Standalone Editor: Port paste API step 1 * Standalone Editor: Port paste API step 2 * improve * improve * Table Fidelity improvement: Width Attribute and Cellpadding attribute (#2284) * init * add test * update name of test * adjust link selection * Standalone Editor: Port paste API step 3 (#2281) * Standalone Editor: Port paste API step 1 * Standalone Editor: Port paste API step 2 * Standalone Editor: Port paste API step 3 * improve * improve * Improve * Improve * Adjust Selection on Cut/Copy first table cell (#2287) * init * fix * address comments * address additional scenario * adjust space for underline * adjust space for underline * fix multiple blocks * fixes * Standalone Editor: Port paste API step 4 (#2282) * Standalone Editor: Port paste API step 1 * Standalone Editor: Port paste API step 2 * Standalone Editor: Port paste API step 3 * Standalone Editor: Port paste API step 4 * improve * improve * fix test * Standalone Editor: Decouple core package from roosterjs-editor-dom (#2283) * Standalone Editor: Port paste API step 1 * Standalone Editor: Port paste API step 2 * Standalone Editor: Port paste API step 3 * Standalone Editor: Port paste API step 4 * Standalone Editor: Decouple core package from roosterjs-editor-dom * Set Deprecated font color to black instead of undefined (#2290) * init * use jasmine anything * Remove negative margins from Word (#2277) * Remove negative margins from Word * remove * Standalone Editor step 1 (#2291) * Fix GetFormatState not returning Font size after paste (#2299) * init * fix build * Standalone Editor step 2 (#2292) * keyboard input on mac * fix test * change isMac for isIME * iscomposing * Support rem unit (#2300) * Support rem unit * Use already existing case * justify-content-api * aligment list item * add justify to map * Content Model: Improve paste and sanitization behavior (#2304) * Fix couple of issues in Word Desktop Paste (#2311) * init * fix build * try fix build * fix again * Add a test * Re-activate tested without model check * Change borderLeft/Right to borderInlineStart/End (#2286) * fix RTL border application * fix table direction change * add tests * Update versions --------- Co-authored-by: Júlia Roldi <juliaroldi@microsoft.com> Co-authored-by: Jiuqing Song <jisong@microsoft.com> Co-authored-by: Bryan Valverde U <bvalverde@microsoft.com> Co-authored-by: Julia Roldi <87443959+juliaroldi@users.noreply.github.com> Co-authored-by: Andres-CT98 <107568016+Andres-CT98@users.noreply.github.com>
1 parent b333ded commit 5d48fd9

157 files changed

Lines changed: 10451 additions & 3247 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

demo/scripts/controls/ribbonButtons/contentModel/ContentModelRibbon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from 'react';
22
import { alignCenterButton } from './alignCenterButton';
3+
import { alignJustifyButton } from './alignJustifyButton';
34
import { alignLeftButton } from './alignLeftButton';
45
import { alignRightButton } from './alignRightButton';
56
import { backgroundColorButton } from './backgroundColorButton';
@@ -83,6 +84,7 @@ const buttons = [
8384
alignLeftButton,
8485
alignCenterButton,
8586
alignRightButton,
87+
alignJustifyButton,
8688
insertLinkButton,
8789
removeLinkButton,
8890
insertTableButton,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { isContentModelEditor } from 'roosterjs-content-model-editor';
2+
import { RibbonButton } from 'roosterjs-react';
3+
import { setAlignment } from 'roosterjs-content-model-api';
4+
5+
/**
6+
* @internal
7+
* "Align justify" button on the format ribbon
8+
*/
9+
export const alignJustifyButton: RibbonButton<'buttonNameAlignJustify'> = {
10+
key: 'buttonNameAlignJustify',
11+
unlocalizedText: 'Align justify',
12+
iconName: 'AlignJustify',
13+
onClick: editor => {
14+
if (isContentModelEditor(editor)) {
15+
setAlignment(editor, 'justify');
16+
}
17+
return true;
18+
},
19+
};

packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelAlignment.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import type {
77
} from 'roosterjs-content-model-types';
88

99
const ResultMap: Record<
10-
'left' | 'center' | 'right',
11-
Record<'ltr' | 'rtl', 'start' | 'center' | 'end'>
10+
'left' | 'center' | 'right' | 'justify',
11+
Record<'ltr' | 'rtl', 'start' | 'center' | 'end' | 'justify'>
1212
> = {
1313
left: {
1414
ltr: 'start',
@@ -22,6 +22,10 @@ const ResultMap: Record<
2222
ltr: 'end',
2323
rtl: 'start',
2424
},
25+
justify: {
26+
ltr: 'justify',
27+
rtl: 'justify',
28+
},
2529
};
2630

2731
const TableAlignMap: Record<
@@ -47,7 +51,7 @@ const TableAlignMap: Record<
4751
*/
4852
export function setModelAlignment(
4953
model: ContentModelDocument,
50-
alignment: 'left' | 'center' | 'right'
54+
alignment: 'left' | 'center' | 'right' | 'justify'
5155
) {
5256
const paragraphOrListItemOrTable = getOperationalBlocks<ContentModelListItem>(
5357
model,
@@ -56,15 +60,21 @@ export function setModelAlignment(
5660
);
5761

5862
paragraphOrListItemOrTable.forEach(({ block }) => {
59-
const newAligment = ResultMap[alignment][block.format.direction == 'rtl' ? 'rtl' : 'ltr'];
60-
if (block.blockType === 'Table') {
63+
const newAlignment = ResultMap[alignment][block.format.direction == 'rtl' ? 'rtl' : 'ltr'];
64+
if (block.blockType === 'Table' && alignment !== 'justify') {
6165
alignTable(
6266
block,
6367
TableAlignMap[alignment][block.format.direction == 'rtl' ? 'rtl' : 'ltr']
6468
);
6569
} else if (block) {
70+
if (block.blockType === 'BlockGroup' && block.blockGroupType === 'ListItem') {
71+
block.blocks.forEach(b => {
72+
const { format } = b;
73+
format.textAlign = newAlignment;
74+
});
75+
}
6676
const { format } = block;
67-
format.textAlign = newAligment;
77+
format.textAlign = newAlignment;
6878
}
6979
});
7080

packages-content-model/roosterjs-content-model-api/lib/modelApi/block/setModelDirection.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { findListItemsInSameThread } from '../list/findListItemsInSameThread';
2-
import { getOperationalBlocks, isBlockGroupOfType } from 'roosterjs-content-model-core';
2+
import {
3+
applyTableFormat,
4+
getOperationalBlocks,
5+
isBlockGroupOfType,
6+
updateTableCellMetadata,
7+
} from 'roosterjs-content-model-core';
38
import type {
9+
BorderFormat,
10+
ContentModelBlock,
411
ContentModelBlockFormat,
512
ContentModelDocument,
613
ContentModelListItem,
@@ -30,14 +37,18 @@ export function setModelDirection(model: ContentModelDocument, direction: 'ltr'
3037
item.blocks.forEach(block => internalSetDirection(block.format, direction));
3138
});
3239
} else if (block) {
33-
internalSetDirection(block.format, direction);
40+
internalSetDirection(block.format, direction, block);
3441
}
3542
});
3643

3744
return paragraphOrListItemOrTable.length > 0;
3845
}
3946

40-
function internalSetDirection(format: ContentModelBlockFormat, direction: 'ltr' | 'rtl') {
47+
function internalSetDirection(
48+
format: ContentModelBlockFormat,
49+
direction: 'ltr' | 'rtl',
50+
block?: ContentModelBlock
51+
) {
4152
const wasRtl = format.direction == 'rtl';
4253
const isRtl = direction == 'rtl';
4354

@@ -46,20 +57,39 @@ function internalSetDirection(format: ContentModelBlockFormat, direction: 'ltr'
4657

4758
// Adjust margin when change direction
4859
// TODO: make margin and padding direction-aware, like what we did for textAlign. So no need to adjust them here
49-
// TODO: Do we also need to handle border here?
5060
const marginLeft = format.marginLeft;
5161
const paddingLeft = format.paddingLeft;
5262

5363
setProperty(format, 'marginLeft', format.marginRight);
5464
setProperty(format, 'marginRight', marginLeft);
5565
setProperty(format, 'paddingLeft', format.paddingRight);
5666
setProperty(format, 'paddingRight', paddingLeft);
67+
68+
// If whole Table direction changed, flip cell side borders
69+
if (block && block.blockType == 'Table') {
70+
block.rows.forEach(row => {
71+
row.cells.forEach(cell => {
72+
// Optimise by skipping cells with unchanged borders
73+
updateTableCellMetadata(cell, metadata => {
74+
if (metadata?.borderOverride) {
75+
const storeBorderLeft = cell.format.borderLeft;
76+
setProperty(cell.format, 'borderLeft', cell.format.borderRight);
77+
setProperty(cell.format, 'borderRight', storeBorderLeft);
78+
}
79+
return metadata;
80+
});
81+
});
82+
});
83+
84+
// Apply changed borders
85+
applyTableFormat(block, undefined /* newFormat */, true /* keepCellShade*/);
86+
}
5787
}
5888
}
5989

6090
function setProperty(
61-
format: MarginFormat & PaddingFormat,
62-
key: keyof (MarginFormat & PaddingFormat),
91+
format: MarginFormat & PaddingFormat & BorderFormat,
92+
key: keyof (MarginFormat & PaddingFormat & BorderFormat),
6393
value: string | undefined
6494
) {
6595
if (value) {

packages-content-model/roosterjs-content-model-api/lib/modelApi/common/retrieveModelFormatState.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { parseValueWithUnit } from 'roosterjs-content-model-dom';
12
import {
23
extractBorderValues,
34
getClosestAncestorBlockGroupIndex,
@@ -150,7 +151,13 @@ function retrieveSegmentFormat(
150151
mergeValue(result, 'letterSpacing', mergedFormat.letterSpacing, isFirst);
151152

152153
mergeValue(result, 'fontName', mergedFormat.fontFamily, isFirst);
153-
mergeValue(result, 'fontSize', mergedFormat.fontSize, isFirst);
154+
mergeValue(
155+
result,
156+
'fontSize',
157+
mergedFormat.fontSize,
158+
isFirst,
159+
val => parseValueWithUnit(val, undefined, 'pt') + 'pt'
160+
);
154161
mergeValue(result, 'backgroundColor', mergedFormat.backgroundColor, isFirst);
155162
mergeValue(result, 'textColor', mergedFormat.textColor, isFirst);
156163
mergeValue(result, 'fontWeight', mergedFormat.fontWeight, isFirst);
@@ -232,13 +239,14 @@ function mergeValue<K extends keyof ContentModelFormatState>(
232239
format: ContentModelFormatState,
233240
key: K,
234241
newValue: ContentModelFormatState[K] | undefined,
235-
isFirst: boolean
242+
isFirst: boolean,
243+
parseFn: (val: ContentModelFormatState[K]) => ContentModelFormatState[K] = val => val
236244
) {
237245
if (isFirst) {
238246
if (newValue !== undefined) {
239247
format[key] = newValue;
240248
}
241-
} else if (newValue !== format[key]) {
249+
} else if (parseFn(newValue) !== parseFn(format[key])) {
242250
delete format[key];
243251
}
244252
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { createText } from 'roosterjs-content-model-dom';
2+
import { iterateSelections } from 'roosterjs-content-model-core';
3+
import type {
4+
ContentModelDocument,
5+
ContentModelParagraph,
6+
ContentModelText,
7+
} from 'roosterjs-content-model-types';
8+
9+
/**
10+
* If a format cannot be applied to be applied to a trailing space, split the trailing space into a separate segment
11+
* @internal
12+
*/
13+
export function adjustTrailingSpaceSelection(model: ContentModelDocument) {
14+
iterateSelections(model, (_, __, block, segments) => {
15+
if (block?.blockType === 'Paragraph' && segments && segments.length > 0) {
16+
if (
17+
segments.length === 1 &&
18+
segments[0].segmentType === 'Text' &&
19+
shouldSplitTrailingSpace(segments[0])
20+
) {
21+
splitTextSegment(block, segments[0]);
22+
} else {
23+
const lastTextSegment =
24+
segments[segments.length - 1].segmentType === 'SelectionMarker'
25+
? segments[segments.length - 2]
26+
: segments[segments.length - 1];
27+
if (
28+
lastTextSegment &&
29+
lastTextSegment.segmentType === 'Text' &&
30+
shouldSplitTrailingSpace(lastTextSegment)
31+
) {
32+
splitTextSegment(block, lastTextSegment);
33+
}
34+
}
35+
}
36+
37+
return false;
38+
});
39+
}
40+
41+
function shouldSplitTrailingSpace(segment: ContentModelText) {
42+
return segment.isSelected && hasTrailingSpace(segment.text) && !isTrailingSpace(segment.text);
43+
}
44+
45+
function hasTrailingSpace(text: string) {
46+
return text.trimRight() !== text;
47+
}
48+
49+
function isTrailingSpace(text: string) {
50+
return text.trimRight().length == 0;
51+
}
52+
53+
function splitTextSegment(block: ContentModelParagraph, textSegment: Readonly<ContentModelText>) {
54+
const text = textSegment.text.trimRight();
55+
const trailingSpace = textSegment.text.substring(text.length);
56+
const newText = createText(text, textSegment.format, textSegment.link, textSegment.code);
57+
newText.isSelected = true;
58+
const trailingSpaceLink = textSegment.link
59+
? {
60+
...textSegment.link,
61+
format: {
62+
...textSegment.link?.format,
63+
underline: false, // Remove underline for trailing space link
64+
},
65+
}
66+
: undefined;
67+
const trailingSpaceSegment = createText(
68+
trailingSpace,
69+
undefined,
70+
trailingSpaceLink,
71+
textSegment.code
72+
);
73+
trailingSpaceSegment.isSelected = true;
74+
const index = block.segments.indexOf(textSegment);
75+
block.segments.splice(index, 1, newText, trailingSpaceSegment);
76+
}

packages-content-model/roosterjs-content-model-api/lib/publicApi/block/setAlignment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { IStandaloneEditor } from 'roosterjs-content-model-types';
88
*/
99
export default function setAlignment(
1010
editor: IStandaloneEditor,
11-
alignment: 'left' | 'center' | 'right'
11+
alignment: 'left' | 'center' | 'right' | 'justify'
1212
) {
1313
editor.focus();
1414

packages-content-model/roosterjs-content-model-api/lib/publicApi/link/insertLink.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection';
12
import { ChangeSource, getSelectedSegments, mergeModel } from 'roosterjs-content-model-core';
23
import { HtmlSanitizer, matchLink } from 'roosterjs-editor-dom';
34
import type { ContentModelLink, IStandaloneEditor } from 'roosterjs-content-model-types';
@@ -91,6 +92,7 @@ export default function insertLink(
9192
});
9293
}
9394

95+
adjustTrailingSpaceSelection(model);
9496
return segments.length > 0;
9597
},
9698
{

packages-content-model/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection';
12
import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel';
23
import type { IStandaloneEditor } from 'roosterjs-content-model-types';
34

@@ -18,6 +19,8 @@ export default function toggleUnderline(editor: IStandaloneEditor) {
1819
segment.link.format.underline = !!isTurningOn;
1920
}
2021
},
21-
(format, segment) => !!format.underline || !!segment?.link?.format?.underline
22+
(format, segment) => !!format.underline || !!segment?.link?.format?.underline,
23+
false /*includingFormatHolder*/,
24+
adjustTrailingSpaceSelection
2225
);
2326
}

0 commit comments

Comments
 (0)