Skip to content

Commit 7597ae9

Browse files
authored
Merge pull request #2963 from microsoft/u/nguyenvi/version-bump-030725
Version bump to 9.21.0
2 parents d23896b + ed81a46 commit 7597ae9

File tree

58 files changed

+5766
-83
lines changed

Some content is hidden

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

58 files changed

+5766
-83
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ API overrides in Editor options when creating the editor.
8888
`roosterjs-content-model-api` provides APIs for scenario-based operations triggered by
8989
user interaction.
9090

91+
`roosterjs-content-model-markdown` provides API to transform Markdown language in Content Model objects.
92+
9193
## Plugins
9294

9395
Rooster supports plugins. You can use built-in plugins or build your own.

demo/scripts/controlsV2/mainPane/MainPane.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getDarkColor } from 'roosterjs-color-utils';
1515
import { getPresetModelById } from '../sidePane/presets/allPresets/allPresets';
1616
import { getTabs, tabNames } from '../tabs/getTabs';
1717
import { getTheme } from '../theme/themes';
18+
import { MarkdownPanePlugin } from '../sidePane/MarkdownPane/MarkdownPanePlugin';
1819
import { OptionState, UrlPlaceholder } from '../sidePane/editorOptions/OptionState';
1920
import { popoutButton } from '../demoButtons/popoutButton';
2021
import { PresetPlugin } from '../sidePane/presets/PresetPlugin';
@@ -102,6 +103,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
102103
private samplePickerPlugin: SamplePickerPlugin;
103104
private snapshots: Snapshots;
104105
private imageEditPlugin: ImageEditPlugin;
106+
private markdownPanePlugin: MarkdownPanePlugin;
105107

106108
protected sidePane = React.createRef<SidePane>();
107109
protected updateContentPlugin: UpdateContentPlugin;
@@ -140,6 +142,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
140142
this.formatPainterPlugin = new FormatPainterPlugin();
141143
this.samplePickerPlugin = new SamplePickerPlugin();
142144
this.imageEditPlugin = new ImageEditPlugin();
145+
this.markdownPanePlugin = new MarkdownPanePlugin();
143146

144147
this.state = {
145148
showSidePane: window.location.hash != '',
@@ -481,6 +484,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
481484
this.snapshotPlugin,
482485
this.contentModelPanePlugin,
483486
this.presetPlugin,
487+
this.markdownPanePlugin,
484488
];
485489
}
486490

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.container {
2+
flex-direction: column;
3+
display: flex;
4+
height: 100%;
5+
}
6+
7+
.textArea {
8+
height: 90%;
9+
width: 100%;
10+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as React from 'react';
2+
import { convertMarkdownToContentModel } from 'roosterjs-content-model-markdown';
3+
import { MarkdownPaneProps } from './MarkdownPanePlugin';
4+
import {
5+
createBr,
6+
createParagraph,
7+
createSelectionMarker,
8+
mergeModel,
9+
} from 'roosterjs-content-model-dom';
10+
11+
const styles = require('./MarkdownPane.scss');
12+
13+
export default class MarkdownPane extends React.Component<MarkdownPaneProps> {
14+
private html = React.createRef<HTMLTextAreaElement>();
15+
16+
constructor(props: MarkdownPaneProps) {
17+
super(props);
18+
}
19+
20+
private convert = () => {
21+
const editor = this.props.getEditor();
22+
editor.formatContentModel((model, context) => {
23+
const markdownModel = convertMarkdownToContentModel(this.html.current.value);
24+
mergeModel(model, markdownModel, context);
25+
return true;
26+
});
27+
};
28+
29+
private clear = () => {
30+
this.html.current.value = '';
31+
};
32+
33+
private clearEditor = () => {
34+
const editor = this.props.getEditor();
35+
editor.formatContentModel(model => {
36+
model.blocks = [];
37+
const emptyParagraph = createParagraph();
38+
const marker = createSelectionMarker();
39+
const br = createBr();
40+
emptyParagraph.segments.push(marker);
41+
emptyParagraph.segments.push(br);
42+
model.blocks.push(emptyParagraph);
43+
return true;
44+
});
45+
};
46+
47+
render() {
48+
return (
49+
<div className={styles.container}>
50+
<p>Convert Markdown to content model</p>
51+
<textarea
52+
className={styles.textArea}
53+
title="Plain Text Editor"
54+
ref={this.html}></textarea>
55+
<div>
56+
<button type="button" onClick={this.clear}>
57+
Clear
58+
</button>
59+
<button type="button" onClick={this.clearEditor}>
60+
Clear editor
61+
</button>
62+
<button type="button" onClick={this.convert}>
63+
Convert
64+
</button>
65+
</div>
66+
</div>
67+
);
68+
}
69+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import MarkdownPane from './MarkdownPane';
2+
import { IEditor, PluginEvent } from 'roosterjs-content-model-types';
3+
import { SidePaneElementProps } from '../SidePaneElement';
4+
import { SidePanePluginImpl } from '../SidePanePluginImpl';
5+
6+
export interface MarkdownPaneProps extends SidePaneElementProps {
7+
getEditor: () => IEditor;
8+
}
9+
10+
export class MarkdownPanePlugin extends SidePanePluginImpl<MarkdownPane, MarkdownPaneProps> {
11+
constructor() {
12+
super(MarkdownPane, 'plainText', 'Markdown Editor');
13+
}
14+
15+
onPluginEvent(e: PluginEvent) {}
16+
17+
getComponentProps(base: MarkdownPaneProps) {
18+
return {
19+
...base,
20+
getEditor: () => {
21+
return this.editor;
22+
},
23+
};
24+
}
25+
}

demo/scripts/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"roosterjs-content-model-api": ["packages/roosterjs-content-model-api/lib/index"],
2020
"roosterjs-content-model-plugins": [
2121
"packages/roosterjs-content-model-plugins/lib/index"
22+
],
23+
"roosterjs-content-model-markdown": [
24+
"packages/roosterjs-content-model-markdown/lib/index"
2225
]
2326
}
2427
},

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { alignTable } from '../table/alignTable';
22
import { getOperationalBlocks, mutateBlock } from 'roosterjs-content-model-dom';
3+
import { splitSelectedParagraphByBr } from './splitSelectedParagraphByBr';
34
import type {
45
ContentModelListItem,
56
ReadonlyContentModelDocument,
@@ -53,6 +54,8 @@ export function setModelAlignment(
5354
model: ReadonlyContentModelDocument,
5455
alignment: 'left' | 'center' | 'right' | 'justify'
5556
) {
57+
splitSelectedParagraphByBr(model);
58+
5659
const paragraphOrListItemOrTable = getOperationalBlocks<ContentModelListItem>(
5760
model,
5861
['ListItem'],

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { findListItemsInSameThread } from '../list/findListItemsInSameThread';
2+
import { splitSelectedParagraphByBr } from './splitSelectedParagraphByBr';
23
import {
34
applyTableFormat,
45
getOperationalBlocks,
@@ -19,6 +20,8 @@ import type {
1920
* @internal
2021
*/
2122
export function setModelDirection(model: ReadonlyContentModelDocument, direction: 'ltr' | 'rtl') {
23+
splitSelectedParagraphByBr(model);
24+
2225
const paragraphOrListItemOrTable = getOperationalBlocks<ContentModelListItem>(
2326
model,
2427
['ListItem'],

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { findListItemsInSameThread } from '../list/findListItemsInSameThread';
22
import { getListAnnounceData } from '../list/getListAnnounceData';
3+
import { splitSelectedParagraphByBr } from './splitSelectedParagraphByBr';
34
import {
45
createListLevel,
56
getOperationalBlocks,
@@ -33,6 +34,8 @@ export function setModelIndentation(
3334
length: number = IndentStepInPixel,
3435
context?: FormatContentModelContext
3536
) {
37+
splitSelectedParagraphByBr(model);
38+
3639
const paragraphOrListItem = getOperationalBlocks<ContentModelListItem>(
3740
model,
3841
['ListItem'],
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {
2+
createParagraph,
3+
getSelectedSegmentsAndParagraphs,
4+
mutateBlock,
5+
} from 'roosterjs-content-model-dom';
6+
import type {
7+
ReadonlyContentModelDocument,
8+
ReadonlyContentModelParagraph,
9+
ShallowMutableContentModelParagraph,
10+
} from 'roosterjs-content-model-types';
11+
12+
/**
13+
* @internal
14+
* For all selected paragraphs, if it has BR in middle of other segments, split the paragraph into multiple paragraphs
15+
* @param model The model to process
16+
*/
17+
export function splitSelectedParagraphByBr(model: ReadonlyContentModelDocument) {
18+
const selections = getSelectedSegmentsAndParagraphs(
19+
model,
20+
false /*includingFormatHolder*/,
21+
false /*includingEntity*/
22+
);
23+
24+
for (const [_, para, path] of selections) {
25+
if (para?.segments.some(s => s.segmentType == 'Br')) {
26+
let currentParagraph = shallowColonParagraph(para);
27+
let hasVisibleSegment = false;
28+
const newParagraphs: ShallowMutableContentModelParagraph[] = [];
29+
const parent = mutateBlock(path[0]);
30+
const index = parent.blocks.indexOf(para);
31+
32+
if (index >= 0) {
33+
for (const segment of mutateBlock(para).segments) {
34+
if (segment.segmentType == 'Br') {
35+
if (!hasVisibleSegment) {
36+
currentParagraph.segments.push(segment);
37+
}
38+
39+
if (currentParagraph.segments.length > 0) {
40+
newParagraphs.push(currentParagraph);
41+
}
42+
43+
currentParagraph = shallowColonParagraph(para);
44+
hasVisibleSegment = false;
45+
} else {
46+
currentParagraph.segments.push(segment);
47+
48+
if (segment.segmentType != 'SelectionMarker') {
49+
hasVisibleSegment = true;
50+
}
51+
}
52+
}
53+
54+
if (currentParagraph.segments.length > 0) {
55+
newParagraphs.push(currentParagraph);
56+
}
57+
58+
parent.blocks.splice(index, 1, ...newParagraphs);
59+
}
60+
}
61+
}
62+
}
63+
64+
function shallowColonParagraph(
65+
para: ReadonlyContentModelParagraph
66+
): ShallowMutableContentModelParagraph {
67+
return createParagraph(false /*isImplicit*/, para.format, para.segmentFormat, para.decorator);
68+
}

0 commit comments

Comments
 (0)