-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for paragraph line spacing for content model (#1543)
* Fetch line height from children * Create new content model api * Spacing btn * Fix tests * Remove key from roosterjs-react * testing * Allow segment to hold lineHeight format * Remove lineHeight from segments whenever possible * Fix imports * Remove normalization * Add todo for edge case * Render segment line height
- Loading branch information
Showing
12 changed files
with
305 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
demo/scripts/controls/ribbonButtons/contentModel/spacingButton.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import isContentModelEditor from '../../editor/isContentModelEditor'; | ||
import type { RibbonButton } from 'roosterjs-react'; | ||
import { setSpacing } from 'roosterjs-content-model'; | ||
|
||
const SPACING_OPTIONS = ['1.0', '1.15', '1.5', '2.0']; | ||
const NORMAL_SPACING = 1.2; | ||
const spacingButtonKey = 'buttonNameSpacing'; | ||
|
||
function findClosest(lineHeight?: string) { | ||
if (Number.isNaN(+lineHeight)) { | ||
return ''; | ||
} | ||
const query = +lineHeight / NORMAL_SPACING; | ||
return SPACING_OPTIONS.find(opt => Math.abs(query - +opt) < 0.05); | ||
} | ||
|
||
/** | ||
* @internal | ||
* "Spacing" button on the format ribbon | ||
*/ | ||
export const spacingButton: RibbonButton<typeof spacingButtonKey> = { | ||
key: spacingButtonKey, | ||
unlocalizedText: 'Spacing', | ||
iconName: 'LineSpacing', | ||
dropDownMenu: { | ||
items: SPACING_OPTIONS.reduce((map, size) => { | ||
map[size] = size; | ||
return map; | ||
}, <Record<string, string>>{}), | ||
getSelectedItemKey: formatState => findClosest(formatState.lineHeight), | ||
allowLivePreview: true, | ||
}, | ||
onClick: (editor, size) => { | ||
if (isContentModelEditor(editor)) { | ||
setSpacing(editor, +size * NORMAL_SPACING); | ||
} | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/roosterjs-content-model/lib/publicApi/block/setSpacing.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { IExperimentalContentModelEditor } from '../../publicTypes/IExperimentalContentModelEditor'; | ||
import { formatParagraphWithContentModel } from '../utils/formatParagraphWithContentModel'; | ||
|
||
export default function setSpacing( | ||
editor: IExperimentalContentModelEditor, | ||
spacing: number | string | ||
) { | ||
formatParagraphWithContentModel(editor, 'setSpacing', paragraph => { | ||
paragraph.format.lineHeight = spacing.toString(); | ||
paragraph.segments.forEach(segment => { | ||
if (segment.format.lineHeight) { | ||
delete segment.format.lineHeight; | ||
} | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
221 changes: 221 additions & 0 deletions
221
packages/roosterjs-content-model/test/publicApi/block/setSpacingTest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
import { ContentModelDocument } from '../../../lib/publicTypes/group/ContentModelDocument'; | ||
import { paragraphTestCommon } from './paragraphTestCommon'; | ||
import setSpacing from '../../../lib/publicApi/block/setSpacing'; | ||
|
||
describe('setSpacing', () => { | ||
function runTest( | ||
model: ContentModelDocument, | ||
result: ContentModelDocument, | ||
spacing: number, | ||
calledTimes: number = 1 | ||
) { | ||
paragraphTestCommon( | ||
'setSpacing', | ||
editor => setSpacing(editor, spacing), | ||
model, | ||
result, | ||
calledTimes | ||
); | ||
} | ||
|
||
it('empty content', () => { | ||
runTest( | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [], | ||
}, | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [], | ||
}, | ||
1.5, | ||
0 | ||
); | ||
}); | ||
|
||
it('no selection', () => { | ||
runTest( | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: {}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: {}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
1.5, | ||
0 | ||
); | ||
}); | ||
|
||
it('Collapsed selection', () => { | ||
runTest( | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: {}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
}, | ||
{ | ||
segmentType: 'SelectionMarker', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: { | ||
lineHeight: '1.5', | ||
}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
}, | ||
{ | ||
segmentType: 'SelectionMarker', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
1.5, | ||
1 | ||
); | ||
}); | ||
|
||
it('With selection', () => { | ||
runTest( | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: {}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: { | ||
lineHeight: '1.5', | ||
}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
1.5, | ||
1 | ||
); | ||
}); | ||
|
||
it('Removes line-height from segment children', () => { | ||
runTest( | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: {}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: { | ||
lineHeight: '123', | ||
}, | ||
}, | ||
{ | ||
segmentType: 'SelectionMarker', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
blockGroupType: 'Document', | ||
blocks: [ | ||
{ | ||
blockType: 'Paragraph', | ||
format: { | ||
lineHeight: '1.5', | ||
}, | ||
segments: [ | ||
{ | ||
segmentType: 'Text', | ||
text: 'test', | ||
format: {}, | ||
}, | ||
{ | ||
segmentType: 'SelectionMarker', | ||
format: {}, | ||
isSelected: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
1.5, | ||
1 | ||
); | ||
}); | ||
}); |
Oops, something went wrong.