Skip to content

Commit 5bdb528

Browse files
authored
Merge branch 'master' into u/jisong/fixbulletlist
2 parents e685001 + 2fac28c commit 5bdb528

File tree

8 files changed

+401
-49
lines changed

8 files changed

+401
-49
lines changed
Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
import { addParser } from '../utils/addParser';
2+
import { adjustPercentileLineHeight } from '../parsers/adjustPercentileLineHeightParser';
23
import { getStyleMetadata } from './getStyleMetadata';
34
import { getStyles } from '../utils/getStyles';
5+
import { listLevelParser } from '../parsers/listLevelParser';
46
import { processWordComments } from './processWordComments';
57
import { processWordList } from './processWordLists';
68
import { removeNegativeTextIndentParser } from '../parsers/removeNegativeTextIndentParser';
79
import { setProcessor } from '../utils/setProcessor';
10+
import { wordTableParser } from '../parsers/wordTableParser';
811
import type { WordMetadata } from './WordMetadata';
9-
import type {
10-
BeforePasteEvent,
11-
ContentModelBlockFormat,
12-
ContentModelListItemLevelFormat,
13-
ContentModelTableFormat,
14-
DomToModelContext,
15-
ElementProcessor,
16-
FormatParser,
17-
} from 'roosterjs-content-model-types';
18-
19-
const PERCENTAGE_REGEX = /%/;
20-
// Default line height in browsers according to https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#normal
21-
const DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE = 1.2;
12+
import type { BeforePasteEvent, ElementProcessor } from 'roosterjs-content-model-types';
2213

2314
/**
2415
* @internal
@@ -52,39 +43,3 @@ const wordDesktopElementProcessor = (
5243
}
5344
};
5445
};
55-
56-
function adjustPercentileLineHeight(format: ContentModelBlockFormat, element: HTMLElement): void {
57-
//If the line height is less than the browser default line height, line between the text is going to be too narrow
58-
let parsedLineHeight: number;
59-
if (
60-
PERCENTAGE_REGEX.test(element.style.lineHeight) &&
61-
!isNaN((parsedLineHeight = parseInt(element.style.lineHeight)))
62-
) {
63-
format.lineHeight = (
64-
DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE *
65-
(parsedLineHeight / 100)
66-
).toString();
67-
}
68-
}
69-
70-
const listLevelParser: FormatParser<ContentModelListItemLevelFormat> = (
71-
format: ContentModelListItemLevelFormat,
72-
element: HTMLElement,
73-
_context: DomToModelContext,
74-
defaultStyle: Readonly<Partial<CSSStyleDeclaration>>
75-
) => {
76-
if (element.style.marginLeft != '') {
77-
format.marginLeft = defaultStyle.marginLeft;
78-
}
79-
80-
format.marginBottom = undefined;
81-
};
82-
83-
const wordTableParser: FormatParser<ContentModelTableFormat> = (format, element): void => {
84-
if (format.marginLeft?.startsWith('-')) {
85-
delete format.marginLeft;
86-
}
87-
if (format.htmlAlign) {
88-
delete format.htmlAlign;
89-
}
90-
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { ContentModelBlockFormat } from 'roosterjs-content-model-types';
2+
3+
const PERCENTAGE_REGEX = /%/;
4+
// Default line height in browsers according to https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#normal
5+
const DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE = 1.2;
6+
7+
/**
8+
* @internal
9+
* Parser for adjusting percentage-based line heights and converting 'normal' to a specific percentage
10+
* @param format The block format to modify
11+
* @param element The HTML element being processed
12+
*/
13+
export function adjustPercentileLineHeight(
14+
format: ContentModelBlockFormat,
15+
element: HTMLElement
16+
): void {
17+
// If the line height is less than the browser default line height, line between the text is going to be too narrow
18+
let parsedLineHeight: number;
19+
if (
20+
PERCENTAGE_REGEX.test(element.style.lineHeight) &&
21+
!isNaN((parsedLineHeight = parseInt(element.style.lineHeight)))
22+
) {
23+
format.lineHeight = (
24+
DEFAULT_BROWSER_LINE_HEIGHT_PERCENTAGE *
25+
(parsedLineHeight / 100)
26+
).toString();
27+
} else if (element.style.lineHeight.toLowerCase() === 'normal') {
28+
format.lineHeight = '120%';
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type {
2+
ContentModelListItemLevelFormat,
3+
DomToModelContext,
4+
FormatParser,
5+
} from 'roosterjs-content-model-types';
6+
7+
/**
8+
* @internal
9+
* Parser for processing list level formatting specific to Word Desktop
10+
* @param format The list item level format to modify
11+
* @param element The HTML element being processed
12+
* @param _context The DOM to model context
13+
* @param defaultStyle The default style properties
14+
*/
15+
export const listLevelParser: FormatParser<ContentModelListItemLevelFormat> = (
16+
format: ContentModelListItemLevelFormat,
17+
element: HTMLElement,
18+
_context: DomToModelContext,
19+
defaultStyle: Readonly<Partial<CSSStyleDeclaration>>
20+
) => {
21+
if (element.style.marginLeft !== '') {
22+
format.marginLeft = defaultStyle.marginLeft;
23+
}
24+
25+
format.marginBottom = undefined;
26+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ContentModelTableFormat, FormatParser } from 'roosterjs-content-model-types';
2+
3+
/**
4+
* @internal
5+
* Parser for processing table formatting specific to Word Desktop
6+
* @param format The table format to modify
7+
* @param element The HTML element being processed
8+
*/
9+
export const wordTableParser: FormatParser<ContentModelTableFormat> = (format, element): void => {
10+
if (format.marginLeft?.startsWith('-')) {
11+
delete format.marginLeft;
12+
}
13+
if (format.htmlAlign) {
14+
delete format.htmlAlign;
15+
}
16+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { adjustPercentileLineHeight } from '../../../lib/paste/parsers/adjustPercentileLineHeightParser';
2+
import { ContentModelBlockFormat } from 'roosterjs-content-model-types';
3+
4+
describe('adjustPercentileLineHeight', () => {
5+
let format: ContentModelBlockFormat;
6+
let element: HTMLElement;
7+
8+
beforeEach(() => {
9+
format = {};
10+
element = document.createElement('p');
11+
});
12+
13+
it('should convert percentage line height using browser default multiplier', () => {
14+
element.style.lineHeight = '100%';
15+
adjustPercentileLineHeight(format, element);
16+
expect(format.lineHeight).toBe('1.2');
17+
});
18+
19+
it('should convert percentage line height with different values', () => {
20+
element.style.lineHeight = '150%';
21+
adjustPercentileLineHeight(format, element);
22+
expect(parseFloat(format.lineHeight!)).toBeCloseTo(1.8, 5);
23+
});
24+
25+
it('should convert percentage line height with low values', () => {
26+
element.style.lineHeight = '50%';
27+
adjustPercentileLineHeight(format, element);
28+
expect(parseFloat(format.lineHeight!)).toBeCloseTo(0.6, 5);
29+
});
30+
31+
it('should convert normal line height to 120%', () => {
32+
element.style.lineHeight = 'normal';
33+
adjustPercentileLineHeight(format, element);
34+
expect(format.lineHeight).toBe('120%');
35+
});
36+
37+
it('should convert NORMAL (uppercase) line height to 120% - case insensitive', () => {
38+
element.style.lineHeight = 'NORMAL';
39+
adjustPercentileLineHeight(format, element);
40+
expect(format.lineHeight).toBe('120%');
41+
});
42+
43+
it('should not modify line height when it is not percentage or normal', () => {
44+
element.style.lineHeight = '1.5';
45+
adjustPercentileLineHeight(format, element);
46+
expect(format.lineHeight).toBeUndefined();
47+
});
48+
49+
it('should not modify line height when percentage is invalid', () => {
50+
element.style.lineHeight = 'abc%';
51+
adjustPercentileLineHeight(format, element);
52+
expect(format.lineHeight).toBeUndefined();
53+
});
54+
55+
it('should not modify line height when no line height is set', () => {
56+
adjustPercentileLineHeight(format, element);
57+
expect(format.lineHeight).toBeUndefined();
58+
});
59+
60+
it('should handle mixed case normal values', () => {
61+
element.style.lineHeight = 'Normal';
62+
adjustPercentileLineHeight(format, element);
63+
expect(format.lineHeight).toBe('120%');
64+
});
65+
66+
it('should not affect existing format properties', () => {
67+
format.marginTop = '10px';
68+
format.marginBottom = '5px';
69+
element.style.lineHeight = 'normal';
70+
adjustPercentileLineHeight(format, element);
71+
expect(format.lineHeight).toBe('120%');
72+
expect(format.marginTop).toBe('10px');
73+
expect(format.marginBottom).toBe('5px');
74+
});
75+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ContentModelListItemLevelFormat, DomToModelContext } from 'roosterjs-content-model-types';
2+
import { listLevelParser } from '../../../lib/paste/parsers/listLevelParser';
3+
4+
describe('listLevelParser', () => {
5+
let format: ContentModelListItemLevelFormat;
6+
let element: HTMLElement;
7+
let context: DomToModelContext;
8+
let defaultStyle: Readonly<Partial<CSSStyleDeclaration>>;
9+
10+
beforeEach(() => {
11+
format = {};
12+
element = document.createElement('li');
13+
context = {} as any;
14+
defaultStyle = {};
15+
});
16+
17+
it('should set marginLeft from defaultStyle when element has marginLeft', () => {
18+
element.style.marginLeft = '20px';
19+
defaultStyle = { marginLeft: '15px' };
20+
listLevelParser(format, element, context, defaultStyle);
21+
expect(format.marginLeft).toBe('15px');
22+
expect(format.marginBottom).toBeUndefined();
23+
});
24+
25+
it('should not set marginLeft when element marginLeft is empty', () => {
26+
element.style.marginLeft = '';
27+
defaultStyle = { marginLeft: '15px' };
28+
listLevelParser(format, element, context, defaultStyle);
29+
expect(format.marginLeft).toBeUndefined();
30+
expect(format.marginBottom).toBeUndefined();
31+
});
32+
33+
it('should always set marginBottom to undefined', () => {
34+
format.marginBottom = '10px';
35+
listLevelParser(format, element, context, defaultStyle);
36+
expect(format.marginBottom).toBeUndefined();
37+
});
38+
39+
it('should handle undefined defaultStyle marginLeft', () => {
40+
element.style.marginLeft = '20px';
41+
defaultStyle = {};
42+
listLevelParser(format, element, context, defaultStyle);
43+
expect(format.marginLeft).toBeUndefined();
44+
expect(format.marginBottom).toBeUndefined();
45+
});
46+
47+
it('should preserve other format properties', () => {
48+
format.marginRight = '10px';
49+
format.marginTop = '5px';
50+
element.style.marginLeft = '20px';
51+
defaultStyle = { marginLeft: '15px' };
52+
listLevelParser(format, element, context, defaultStyle);
53+
expect(format.marginRight).toBe('10px');
54+
expect(format.marginTop).toBe('5px');
55+
expect(format.marginLeft).toBe('15px');
56+
expect(format.marginBottom).toBeUndefined();
57+
});
58+
59+
it('should handle element with no marginLeft style', () => {
60+
defaultStyle = { marginLeft: '15px' };
61+
listLevelParser(format, element, context, defaultStyle);
62+
expect(format.marginLeft).toBeUndefined();
63+
expect(format.marginBottom).toBeUndefined();
64+
});
65+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { ContentModelTableFormat, DomToModelContext } from 'roosterjs-content-model-types';
2+
import { wordTableParser } from '../../../lib/paste/parsers/wordTableParser';
3+
4+
describe('wordTableParser', () => {
5+
let format: ContentModelTableFormat;
6+
let element: HTMLElement;
7+
let context: DomToModelContext;
8+
let defaultStyle: Readonly<Partial<CSSStyleDeclaration>>;
9+
10+
beforeEach(() => {
11+
format = {};
12+
element = document.createElement('table');
13+
context = {} as any;
14+
defaultStyle = {};
15+
});
16+
17+
it('should remove marginLeft when it starts with negative value', () => {
18+
format.marginLeft = '-5px';
19+
wordTableParser(format, element, context, defaultStyle);
20+
expect(format.marginLeft).toBeUndefined();
21+
});
22+
23+
it('should remove marginLeft when it starts with negative number', () => {
24+
format.marginLeft = '-10.5px';
25+
wordTableParser(format, element, context, defaultStyle);
26+
expect(format.marginLeft).toBeUndefined();
27+
});
28+
29+
it('should not remove marginLeft when it is positive', () => {
30+
format.marginLeft = '5px';
31+
wordTableParser(format, element, context, defaultStyle);
32+
expect(format.marginLeft).toBe('5px');
33+
});
34+
35+
it('should not remove marginLeft when it is zero', () => {
36+
format.marginLeft = '0px';
37+
wordTableParser(format, element, context, defaultStyle);
38+
expect(format.marginLeft).toBe('0px');
39+
});
40+
41+
it('should remove htmlAlign property', () => {
42+
format.htmlAlign = 'center';
43+
wordTableParser(format, element, context, defaultStyle);
44+
expect(format.htmlAlign).toBeUndefined();
45+
});
46+
47+
it('should handle both negative marginLeft and htmlAlign together', () => {
48+
format.marginLeft = '-8px';
49+
format.htmlAlign = 'start';
50+
wordTableParser(format, element, context, defaultStyle);
51+
expect(format.marginLeft).toBeUndefined();
52+
expect(format.htmlAlign).toBeUndefined();
53+
});
54+
55+
it('should preserve other format properties', () => {
56+
format.marginTop = '10px';
57+
format.marginRight = '5px';
58+
format.marginLeft = '-3px';
59+
format.htmlAlign = 'end';
60+
wordTableParser(format, element, context, defaultStyle);
61+
expect(format.marginTop).toBe('10px');
62+
expect(format.marginRight).toBe('5px');
63+
expect(format.marginLeft).toBeUndefined();
64+
expect(format.htmlAlign).toBeUndefined();
65+
});
66+
67+
it('should handle undefined marginLeft', () => {
68+
format.htmlAlign = 'center';
69+
wordTableParser(format, element, context, defaultStyle);
70+
expect(format.marginLeft).toBeUndefined();
71+
expect(format.htmlAlign).toBeUndefined();
72+
});
73+
74+
it('should handle empty marginLeft string', () => {
75+
format.marginLeft = '';
76+
format.htmlAlign = 'center';
77+
wordTableParser(format, element, context, defaultStyle);
78+
expect(format.marginLeft).toBe('');
79+
expect(format.htmlAlign).toBeUndefined();
80+
});
81+
82+
it('should not affect format when no negative marginLeft or htmlAlign', () => {
83+
format.marginLeft = '5px';
84+
format.marginTop = '10px';
85+
wordTableParser(format, element, context, defaultStyle);
86+
expect(format.marginLeft).toBe('5px');
87+
expect(format.marginTop).toBe('10px');
88+
});
89+
});

0 commit comments

Comments
 (0)