Skip to content

Commit 4c0c12a

Browse files
BryanValverdeUJiuqingSongjuliaroldiCopilot
authored
Bump main version to 9.27.0 (#3025)
* Use FormatContainer to represent DIV with id (#3003) * Fix #3005 (#3007) * Fix a cache issue (#3006) * Refactor getStyleMetadata to not rely on DomCreator and only use String handling (#3010) * Refactor paste plugin to remove unused DOMCreator parameter and enhance style extraction logic * fix test * Change search string to lowercase * Clean image edit when undo (#3015) * undo image * undo image * undo image * Add 'CustomCopyCut' experimental feature to fix some copy cut bugs (#3000) * Add 'CustomCopyCut' experimental feature to enhance copy/cut behavior * Implement pruneUnselectedModel utility for optimizing copy/paste behavior * Try fix iuld * Address comment and fix broken tests * Revert unneeded change * Refactor pruneUnselectedModel --------- Co-authored-by: Jiuqing Song <jisong@microsoft.com> * Demo site: Add preset content for undeleteable anchor (#3014) Co-authored-by: Bryan Valverde U <bvalverde@microsoft.com> * Revert "Refactor getStyleMetadata to not rely on DomCreator and only use Stri…" (#3020) This reverts commit 5bbab35. * Add API playground for createModelFromHTML (#3019) * Add API playground for createModelFromHTML * imporve --------- Co-authored-by: Bryan Valverde U <bvalverde@microsoft.com> * Do not copy div ID on Enter (#3011) * wip * insertCustom * refactor * formatKeys * Add image hidden marker (#3021) Instead of using a dataset to store the isEditing property, a hidden property is now used. To support this, get/set functions and the ImageMarkerFormat were introduced. The imageMarker property can now be accessed through the format property of the image. This change eliminates the need to manually remove the dataset from the image element when extracting content from the DOM. * Include ImageMetadata in FormatState (#3023) * Support List Pasting from PowerPoint Desktop (#3012) * Refactor paste parsers: add removeNegativeTextIndentParser and deprecatedBorderColorParser; update imports and constants for bullet list types * Update packages/roosterjs-content-model-plugins/lib/paste/PowerPoint/processPastedContentFromPowerPoint.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor bullet list constants and improve format parser signatures --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove comments `<!--` and `-->` from styles and re apply fix for Word Desktop Pasting (#3024) * Update dependencies and enhance paste functionality by cleaning HTML comments in style tags * Reapply "Refactor getStyleMetadata to not rely on DomCreator and only use Stri…" (#3020) This reverts commit 32f47bf. * Enhance cleanHtmlComments to handle both HTML comment formats in style tags * Set original DOMPurify * Update packages/roosterjs-content-model-plugins/lib/paste/WordDesktop/getStyleMetadata.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Ensure headEndIndex is valid * address comment * Address comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jiuqing Song <jisong@microsoft.com> * Bump main version to 9.27.0 in versions.json * insert link in the image (#3027) When the image is selected, do not replace the image with the link, add the link to image segment. * square (#3029) Instead of using a square character, this change updates the square style to use the 'square' style. * Normalize default format (#3028) * Normalize default format * improve --------- Co-authored-by: Bryan Valverde U <bvalverde@microsoft.com> * auto link (#3026) * Add margin-inline-start to watermark styles for improved positioning (#3031) --------- Co-authored-by: Jiuqing Song <jisong@microsoft.com> Co-authored-by: Julia Roldi <87443959+juliaroldi@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 39154d7 commit 4c0c12a

File tree

95 files changed

+8277
-602
lines changed

Some content is hidden

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

95 files changed

+8277
-602
lines changed

demo/scripts/controlsV2/mainPane/MainPane.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import {
6969
TableEditPlugin,
7070
WatermarkPlugin,
7171
} from 'roosterjs-content-model-plugins';
72+
import DOMPurify = require('dompurify');
7273

7374
const styles = require('./MainPane.scss');
7475

@@ -191,7 +192,15 @@ export class MainPane extends React.Component<{}, MainPaneState> {
191192
this.updateContentPlugin.update();
192193

193194
const win = window.open(POPOUT_URL, POPOUT_TARGET, POPOUT_FEATURES);
194-
win.document.write(trustedHTMLHandler(POPOUT_HTML));
195+
win.document.write(
196+
(DOMPurify.sanitize(POPOUT_HTML, {
197+
ADD_TAGS: ['head', 'meta', 'iframe'],
198+
ADD_ATTR: ['name', 'content'],
199+
WHOLE_DOCUMENT: true,
200+
ALLOW_UNKNOWN_PROTOCOLS: true,
201+
RETURN_TRUSTED_TYPE: true,
202+
}) as any) as string
203+
);
195204
win.addEventListener('beforeunload', () => {
196205
this.updateContentPlugin.update();
197206

demo/scripts/controlsV2/options/demoUndeletableAnchorParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FormatParser, UndeletableFormat } from 'roosterjs-content-model-types';
22

3-
const DemoUndeletableName = 'DemoUndeletable';
3+
export const DemoUndeletableName = 'DemoUndeletable';
44

55
export function undeletableLinkChecker(a: HTMLAnchorElement): boolean {
66
return a.getAttribute('name') == DemoUndeletableName;

demo/scripts/controlsV2/sidePane/apiPlayground/apiEntries.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as React from 'react';
2+
import CreateModelFromHtmlPane from './createModelFromHtml/CreateModelFromHtmlPane';
3+
import InsertCustomContainerPane from './insertCustomContainer/InsertCustomContainerPane';
24
import InsertEntityPane from './insertEntity/InsertEntityPane';
35
import PastePane from './paste/PastePane';
46
import { ApiPaneProps, ApiPlaygroundComponent } from './ApiPaneProps';
@@ -24,6 +26,14 @@ const apiEntries: { [key: string]: ApiEntry } = {
2426
name: 'Paste',
2527
component: PastePane,
2628
},
29+
createModelFromHtml: {
30+
name: 'Create Model from HTML',
31+
component: CreateModelFromHtmlPane,
32+
},
33+
customContainer: {
34+
name: 'Insert Custom Container',
35+
component: InsertCustomContainerPane,
36+
},
2737
more: {
2838
name: 'Coming soon...',
2939
},
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.textarea {
2+
outline: none;
3+
min-height: 200px;
4+
width: 90%;
5+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as React from 'react';
2+
import { ApiPaneProps } from '../ApiPaneProps';
3+
import { cloneModel } from 'roosterjs-content-model-dom';
4+
import { ContentModelDocument } from 'roosterjs-content-model-types';
5+
import { ContentModelDocumentView } from '../../contentModel/components/model/ContentModelDocumentView';
6+
import { createModelFromHtml } from 'roosterjs-content-model-core';
7+
8+
const styles = require('./CreateModelFromHtmlPane.scss');
9+
10+
interface CreateModelFromHtmlPaneState {
11+
model: ContentModelDocument | null;
12+
}
13+
14+
export default class CreateModelFromHtmlPane extends React.Component<
15+
ApiPaneProps,
16+
CreateModelFromHtmlPaneState
17+
> {
18+
private html = React.createRef<HTMLTextAreaElement>();
19+
20+
constructor(props: ApiPaneProps) {
21+
super(props);
22+
this.state = {
23+
model: null,
24+
};
25+
}
26+
27+
render() {
28+
return (
29+
<>
30+
<div>HTML:</div>
31+
<div>
32+
<textarea className={styles.textarea} ref={this.html}></textarea>
33+
</div>
34+
<div>
35+
<button onClick={this.createModel}>Create Model</button>
36+
</div>
37+
<div>
38+
{this.state.model ? <ContentModelDocumentView doc={this.state.model} /> : null}
39+
</div>
40+
<div>
41+
<button onClick={this.setModel} disabled={!this.state.model}>
42+
Set Model into Editor
43+
</button>
44+
</div>
45+
</>
46+
);
47+
}
48+
49+
private createModel = () => {
50+
const html = this.html.current?.value || '';
51+
52+
if (html) {
53+
const model = createModelFromHtml(
54+
html,
55+
undefined,
56+
this.props.getEditor().getDOMCreator()
57+
);
58+
this.setState({ model });
59+
} else {
60+
this.setState({ model: null });
61+
}
62+
};
63+
64+
private setModel = () => {
65+
if (this.state.model) {
66+
this.props.getEditor().formatContentModel(model => {
67+
model.blocks = cloneModel(this.state.model).blocks;
68+
69+
return true;
70+
});
71+
}
72+
};
73+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.textarea {
2+
height: 200px;
3+
}
4+
5+
.blockContent {
6+
display: grid;
7+
row-gap: 5px;
8+
padding-bottom: 5px;
9+
}
10+
11+
.results {
12+
overflow-wrap: anywhere;
13+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import * as React from 'react';
2+
import { ApiPaneProps } from '../ApiPaneProps';
3+
import { createFormatContainer, mergeModel } from 'roosterjs-content-model-dom';
4+
import { createModelFromHtml } from 'roosterjs-content-model-core';
5+
import { trustedHTMLHandler } from '../../../../utils/trustedHTMLHandler';
6+
import {
7+
ContentModelDocument,
8+
ContentModelFormatContainer,
9+
IEditor,
10+
} from 'roosterjs-content-model-types';
11+
12+
interface InsertCustomContainerPaneState {
13+
containers: {
14+
[id: string]: ContentModelFormatContainer;
15+
};
16+
}
17+
18+
const styles = require('./InsertCustomContainerPane.scss');
19+
20+
export default class InsertCustomContainerPane extends React.Component<
21+
ApiPaneProps,
22+
InsertCustomContainerPaneState
23+
> {
24+
public html = React.createRef<HTMLTextAreaElement>();
25+
private containerId = React.createRef<HTMLInputElement>();
26+
private searchId = React.createRef<HTMLInputElement>();
27+
private result = React.createRef<HTMLParagraphElement>();
28+
29+
constructor(props: ApiPaneProps) {
30+
super(props);
31+
this.state = {
32+
containers: {},
33+
};
34+
}
35+
36+
private insertCustomContainer = () => {
37+
const model = createModelFromHtml(this.html.current.value, undefined, trustedHTMLHandler);
38+
const container = createFormatContainer('div');
39+
const containerId = this.containerId.current.value;
40+
container.format.id = containerId;
41+
container.blocks = [...model.blocks];
42+
this.state.containers[containerId] = container;
43+
model.blocks = [container];
44+
const editor = this.props.getEditor();
45+
insertContainer(editor, model);
46+
};
47+
48+
private getCustomContainer = () => {
49+
const id = this.searchId.current.value;
50+
const container = this.state.containers[id];
51+
if (container) {
52+
this.result.current.innerText = JSON.stringify(container);
53+
} else {
54+
this.result.current.innerText = 'No container found';
55+
}
56+
};
57+
58+
render() {
59+
return (
60+
<>
61+
<div className={styles.blockContent}>
62+
<label htmlFor="containerId"> Insert container</label>
63+
<input
64+
ref={this.containerId}
65+
title="Container Id"
66+
id="containerId"
67+
type="text"
68+
placeholder="Container Id"
69+
/>
70+
<textarea
71+
ref={this.html}
72+
className={styles.textarea}
73+
title="Custom Content"
74+
name="Content"
75+
id="customContent"
76+
placeholder="Insert HTML Content"></textarea>
77+
<button onClick={this.insertCustomContainer} type="button">
78+
Insert container
79+
</button>
80+
</div>
81+
82+
<div className={styles.blockContent}>
83+
<label htmlFor="containerId">Id:</label>
84+
<input ref={this.searchId} title="Container Id" id="containerId" type="text" />
85+
<button onClick={this.getCustomContainer} type="button">
86+
Get Custom Container
87+
</button>
88+
<label htmlFor="results"> Results:</label>
89+
<p className={styles.results} ref={this.result} id="results"></p>
90+
</div>
91+
</>
92+
);
93+
}
94+
}
95+
96+
function insertContainer(editor: IEditor, newModel: ContentModelDocument) {
97+
editor.formatContentModel((model, context) => {
98+
mergeModel(model, newModel, context, {
99+
mergeFormat: 'mergeAll',
100+
});
101+
return true;
102+
});
103+
}

demo/scripts/controlsV2/sidePane/apiPlayground/insertEntity/InsertEntityPane.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { ApiPaneProps } from '../ApiPaneProps';
33
import { insertEntity } from 'roosterjs-content-model-api';
4+
import { moveChildNodes } from 'roosterjs-content-model-dom';
45
import { trustedHTMLHandler } from '../../../../utils/trustedHTMLHandler';
56
import {
67
ContentModelEntity,
@@ -98,7 +99,8 @@ export default class InsertEntityPane extends React.Component<ApiPaneProps, Inse
9899
private insertEntity = () => {
99100
const entityType = this.entityType.current.value;
100101
const node = document.createElement('span');
101-
node.innerHTML = trustedHTMLHandler(this.html.current.value);
102+
103+
moveChildNodes(node, trustedHTMLHandler.htmlToDOM(this.html.current.value).body);
102104
const isBlock = this.styleBlock.current.checked;
103105
const focusAfterEntity = this.focusAfterEntity.current.checked;
104106
const insertAtTop = this.posTop.current.checked;

demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ const initialState: OptionState = {
6161
handleTabKey: true,
6262
},
6363
customReplacements: emojiReplacements,
64-
experimentalFeatures: new Set<ExperimentalFeature>(['PersistCache', 'HandleEnterKey']),
64+
experimentalFeatures: new Set<ExperimentalFeature>([
65+
'PersistCache',
66+
'HandleEnterKey',
67+
'CustomCopyCut',
68+
]),
6569
};
6670

6771
export class EditorOptionsPlugin extends SidePanePluginImpl<OptionsPane, OptionPaneProps> {

demo/scripts/controlsV2/sidePane/editorOptions/ExperimentalFeatures.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class ExperimentalFeatures extends React.Component<DefaultFormatProps, {}
1313
<>
1414
{this.renderFeature('PersistCache')}
1515
{this.renderFeature('HandleEnterKey')}
16+
{this.renderFeature('CustomCopyCut')}
1617
</>
1718
);
1819
}

0 commit comments

Comments
 (0)