Skip to content

Commit 713607e

Browse files
authored
Merge pull request #2093 from microsoft/romasha/versionbump921
Bump RoosterJS version to 8.56 and Content Model to 0.16
2 parents 54e3f91 + 0c37988 commit 713607e

File tree

160 files changed

+4561
-3015
lines changed

Some content is hidden

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

160 files changed

+4561
-3015
lines changed

demo/scripts/controls/ContentModelEditorMainPane.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import * as React from 'react';
22
import * as ReactDOM from 'react-dom';
33
import ApiPlaygroundPlugin from './sidePane/contentModelApiPlayground/ApiPlaygroundPlugin';
44
import ContentModelEditorOptionsPlugin from './sidePane/editorOptions/ContentModelEditorOptionsPlugin';
5+
import ContentModelEventViewPlugin from './sidePane/eventViewer/ContentModelEventViewPlugin';
56
import ContentModelFormatPainterPlugin from './contentModel/plugins/ContentModelFormatPainterPlugin';
67
import ContentModelFormatStatePlugin from './sidePane/formatState/ContentModelFormatStatePlugin';
78
import ContentModelPanePlugin from './sidePane/contentModel/ContentModelPanePlugin';
89
import ContentModelRibbon from './ribbonButtons/contentModel/ContentModelRibbon';
9-
import EventViewPlugin from './sidePane/eventViewer/EventViewPlugin';
1010
import getToggleablePlugins from './getToggleablePlugins';
1111
import MainPaneBase from './MainPaneBase';
1212
import SampleEntityPlugin from './sampleEntity/SampleEntityPlugin';
@@ -84,7 +84,7 @@ const DarkTheme: PartialTheme = {
8484
class ContentModelEditorMainPane extends MainPaneBase {
8585
private formatStatePlugin: ContentModelFormatStatePlugin;
8686
private editorOptionPlugin: ContentModelEditorOptionsPlugin;
87-
private eventViewPlugin: EventViewPlugin;
87+
private eventViewPlugin: ContentModelEventViewPlugin;
8888
private apiPlaygroundPlugin: ApiPlaygroundPlugin;
8989
private ContentModelPanePlugin: ContentModelPanePlugin;
9090
private ribbonPlugin: RibbonPlugin;
@@ -100,7 +100,7 @@ class ContentModelEditorMainPane extends MainPaneBase {
100100

101101
this.formatStatePlugin = new ContentModelFormatStatePlugin();
102102
this.editorOptionPlugin = new ContentModelEditorOptionsPlugin();
103-
this.eventViewPlugin = new EventViewPlugin();
103+
this.eventViewPlugin = new ContentModelEventViewPlugin();
104104
this.apiPlaygroundPlugin = new ApiPlaygroundPlugin();
105105
this.snapshotPlugin = new SnapshotPlugin();
106106
this.ContentModelPanePlugin = new ContentModelPanePlugin();

demo/scripts/controls/sidePane/editorOptions/ContentModelEditorOptionsPlugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const initialState: BuildInPluginState = {
2929
watermarkText: 'Type content here ...',
3030
forcePreserveRatio: false,
3131
experimentalFeatures: [
32-
ExperimentalFeatures.AutoFormatList,
3332
ExperimentalFeatures.InlineEntityReadOnlyDelimiters,
3433
ExperimentalFeatures.ContentModelPaste,
3534
],

demo/scripts/controls/sidePane/editorOptions/ContentModelExperimentalFeatures.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ export interface ExperimentalFeaturesProps {
1010

1111
const FeatureNames: Partial<Record<ExperimentalFeatures, string>> = {
1212
[ExperimentalFeatures.TabKeyTextFeatures]: 'Additional functionality to Tab Key',
13-
[ExperimentalFeatures.AutoFormatList]:
14-
'Trigger formatting by a especial characters. Ex: (A), 1. i).',
1513
[ExperimentalFeatures.ReuseAllAncestorListElements]:
1614
"Reuse ancestor list elements even if they don't match the types from the list item.",
1715
[ExperimentalFeatures.DeleteTableWithBackspace]:

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ const initialState: BuildInPluginState = {
2828
linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder,
2929
watermarkText: 'Type content here ...',
3030
forcePreserveRatio: false,
31-
experimentalFeatures: [
32-
ExperimentalFeatures.AutoFormatList,
33-
ExperimentalFeatures.InlineEntityReadOnlyDelimiters,
34-
],
31+
experimentalFeatures: [ExperimentalFeatures.InlineEntityReadOnlyDelimiters],
3532
isRtl: false,
3633
tableFeaturesContainerSelector: '#' + 'EditorContainer',
3734
};

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ export interface ExperimentalFeaturesProps {
1010

1111
const FeatureNames: Partial<Record<ExperimentalFeatures, string>> = {
1212
[ExperimentalFeatures.TabKeyTextFeatures]: 'Additional functionality to Tab Key',
13-
[ExperimentalFeatures.AutoFormatList]:
14-
'Trigger formatting by a especial characters. Ex: (A), 1. i).',
1513
[ExperimentalFeatures.ReuseAllAncestorListElements]:
1614
"Reuse ancestor list elements even if they don't match the types from the list item.",
1715
[ExperimentalFeatures.DeleteTableWithBackspace]:
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import * as React from 'react';
2+
import { ContentModelContentChangedEvent } from 'roosterjs-content-model-editor';
3+
import { EntityOperation, PluginEvent, PluginEventType } from 'roosterjs-editor-types';
4+
import { SidePaneElementProps } from '../SidePaneElement';
5+
import {
6+
getObjectKeys,
7+
getTagOfNode,
8+
HtmlSanitizer,
9+
readFile,
10+
safeInstanceOf,
11+
} from 'roosterjs-editor-dom';
12+
13+
const styles = require('./EventViewPane.scss');
14+
15+
export interface EventEntry {
16+
index: number;
17+
time: Date;
18+
event: PluginEvent;
19+
}
20+
21+
export interface EventViewPaneState {
22+
displayCount: number;
23+
currentIndex: number;
24+
}
25+
26+
const EventTypeMap: { [key in PluginEventType]: string } = {
27+
[PluginEventType.BeforeDispose]: 'BeforeDispose',
28+
[PluginEventType.BeforePaste]: 'BeforePaste',
29+
[PluginEventType.CompositionEnd]: 'CompositionEnd',
30+
[PluginEventType.ContentChanged]: 'ContentChanged',
31+
[PluginEventType.EditorReady]: 'EditorReady',
32+
[PluginEventType.EntityOperation]: 'EntityOperation',
33+
[PluginEventType.ExtractContentWithDom]: 'ExtractContentWithDom',
34+
[PluginEventType.KeyDown]: 'KeyDown',
35+
[PluginEventType.KeyPress]: 'KeyPress',
36+
[PluginEventType.KeyUp]: 'KeyUp',
37+
[PluginEventType.MouseDown]: 'MouseDown',
38+
[PluginEventType.MouseUp]: 'MouseUp',
39+
[PluginEventType.Input]: 'Input',
40+
[PluginEventType.PendingFormatStateChanged]: 'PendingFormatStateChanged',
41+
[PluginEventType.Scroll]: 'Scroll',
42+
[PluginEventType.BeforeCutCopy]: 'BeforeCutCopy',
43+
[PluginEventType.ContextMenu]: 'ContextMenu',
44+
[PluginEventType.EnteredShadowEdit]: 'EnteredShadowEdit',
45+
[PluginEventType.LeavingShadowEdit]: 'LeavingShadowEdit',
46+
[PluginEventType.EditImage]: 'EditImage',
47+
[PluginEventType.BeforeSetContent]: 'BeforeSetContent',
48+
[PluginEventType.ZoomChanged]: 'ZoomChanged',
49+
[PluginEventType.SelectionChanged]: 'SelectionChanged',
50+
[PluginEventType.BeforeKeyboardEditing]: 'BeforeKeyboardEditing',
51+
};
52+
53+
const EntityOperationMap: { [key in EntityOperation]: string } = {
54+
[EntityOperation.AddShadowRoot]: 'AddShadowRoot',
55+
[EntityOperation.RemoveShadowRoot]: 'RemoveShadowRoot',
56+
[EntityOperation.Click]: 'Click',
57+
[EntityOperation.ContextMenu]: 'ContextMenu',
58+
[EntityOperation.Escape]: 'Escape',
59+
[EntityOperation.NewEntity]: 'NewEntity',
60+
[EntityOperation.Overwrite]: 'Overwrite',
61+
[EntityOperation.PartialOverwrite]: 'PartialOverwrite',
62+
[EntityOperation.RemoveFromEnd]: 'RemoveFromEnd',
63+
[EntityOperation.RemoveFromStart]: 'RemoveFromStart',
64+
[EntityOperation.ReplaceTemporaryContent]: 'ReplaceTemporaryContent',
65+
[EntityOperation.UpdateEntityState]: 'UpdateEntityState',
66+
};
67+
68+
export default class ContentModelEventViewPane extends React.Component<
69+
SidePaneElementProps,
70+
EventViewPaneState
71+
> {
72+
private events: EventEntry[] = [];
73+
private displayCount = React.createRef<HTMLSelectElement>();
74+
private lastIndex = 0;
75+
76+
constructor(props: SidePaneElementProps) {
77+
super(props);
78+
this.state = {
79+
displayCount: 20,
80+
currentIndex: -1,
81+
};
82+
}
83+
84+
render() {
85+
let displayCount = Math.min(this.events.length, this.state.displayCount);
86+
let displayedEvents =
87+
displayCount > 0 ? this.events.slice(this.events.length - displayCount) : [];
88+
displayedEvents = displayedEvents.reverse();
89+
90+
return (
91+
<>
92+
<div>
93+
Show item count:
94+
<select
95+
defaultValue={this.state.displayCount.toString()}
96+
ref={this.displayCount}
97+
onChange={this.onDisplayCountChanged}>
98+
<option value={'0'}>Disabled</option>
99+
<option value={'20'}>20</option>
100+
<option value={'50'}>50</option>
101+
<option value={'100'}>100</option>
102+
</select>{' '}
103+
<button onClick={this.clear}>Clear all</button>
104+
</div>
105+
<div>
106+
{displayedEvents.map(event => (
107+
<details key={event.index.toString()}>
108+
<summary>
109+
{`${event.time.getHours()}:${event.time.getMinutes()}:${event.time.getSeconds()}.${event.time.getMilliseconds()} `}
110+
{EventTypeMap[event.event.eventType]}
111+
</summary>
112+
<div className={styles.eventContent}>
113+
{this.renderEvent(event.event)}
114+
</div>
115+
</details>
116+
))}
117+
</div>
118+
</>
119+
);
120+
}
121+
122+
addEvent(event: PluginEvent) {
123+
if (this.state.displayCount > 0) {
124+
if (event.eventType == PluginEventType.BeforePaste) {
125+
const sanitizer = new HtmlSanitizer(event.sanitizingOption);
126+
const fragment = event.fragment.cloneNode(true /*deep*/) as DocumentFragment;
127+
128+
sanitizer.convertGlobalCssToInlineCss(fragment);
129+
sanitizer.sanitize(fragment);
130+
(event.clipboardData as any).html = this.getHtml(fragment);
131+
}
132+
133+
this.events.push({
134+
time: new Date(),
135+
event: event,
136+
index: this.lastIndex++,
137+
});
138+
139+
while (this.events.length > 100) {
140+
this.events.shift();
141+
}
142+
this.setState({
143+
currentIndex: this.lastIndex,
144+
});
145+
}
146+
}
147+
148+
private renderEvent(event: PluginEvent): JSX.Element {
149+
switch (event.eventType) {
150+
case PluginEventType.KeyDown:
151+
case PluginEventType.KeyPress:
152+
case PluginEventType.KeyUp:
153+
return (
154+
<span>
155+
Key=
156+
{event.rawEvent.which}
157+
</span>
158+
);
159+
160+
case PluginEventType.MouseDown:
161+
case PluginEventType.MouseUp:
162+
case PluginEventType.ContextMenu:
163+
return (
164+
<span>
165+
Button=
166+
{event.rawEvent.button}, SrcElement=
167+
{event.rawEvent.target && getTagOfNode(event.rawEvent.target as Node)},
168+
PageX=
169+
{event.rawEvent.pageX}, PageY=
170+
{event.rawEvent.pageY}
171+
</span>
172+
);
173+
174+
case PluginEventType.ContentChanged:
175+
return (
176+
<span>
177+
Source=
178+
{event.source}, Data=
179+
{event.data && event.data.toString && event.data.toString()}
180+
{!!(event as ContentModelContentChangedEvent).contentModel && (
181+
<details>
182+
<summary>Content Model</summary>
183+
<pre className={styles.eventContent}>
184+
{JSON.stringify(
185+
(event as ContentModelContentChangedEvent).contentModel,
186+
(key, value) =>
187+
safeInstanceOf(value, 'Node')
188+
? Object.prototype.toString.apply(value)
189+
: key == 'src'
190+
? value.length > 100
191+
? value.substring(0, 97) + '...'
192+
: value
193+
: value,
194+
2
195+
)}
196+
</pre>
197+
</details>
198+
)}
199+
</span>
200+
);
201+
202+
case PluginEventType.BeforePaste:
203+
return (
204+
<span>
205+
Types=
206+
{event.clipboardData.types.join()}
207+
{this.renderPasteContent('Plain text', event.clipboardData.text)}
208+
{this.renderPasteContent(
209+
'Sanitized HTML',
210+
(event.clipboardData as any).html
211+
)}
212+
{this.renderPasteContent('Original HTML', event.clipboardData.rawHtml)}
213+
{this.renderPasteContent('Image', event.clipboardData.image, img => (
214+
<img
215+
ref={ref => ref && this.renderImage(ref, img)}
216+
className={styles.img}
217+
/>
218+
))}
219+
{this.renderPasteContent(
220+
'LinkPreview',
221+
event.clipboardData.linkPreview
222+
? JSON.stringify(event.clipboardData.linkPreview)
223+
: ''
224+
)}
225+
Paste from keyboard or native context menu:
226+
{event.clipboardData.pasteNativeEvent ? ' true' : ' false'}
227+
{getObjectKeys(event.clipboardData.customValues).map(contentType =>
228+
this.renderPasteContent(
229+
contentType,
230+
event.clipboardData.customValues[contentType]
231+
)
232+
)}
233+
</span>
234+
);
235+
case PluginEventType.PendingFormatStateChanged:
236+
const formatState = event.formatState;
237+
const keys = getObjectKeys(formatState);
238+
return <span>{keys.map(key => `${key}=${event.formatState[key]}; `)}</span>;
239+
240+
case PluginEventType.EntityOperation:
241+
const {
242+
operation,
243+
entity: { id, type },
244+
} = event;
245+
return (
246+
<span>
247+
Operation={EntityOperationMap[operation]} Type={type}; Id={id}
248+
</span>
249+
);
250+
251+
case PluginEventType.BeforeCutCopy:
252+
const { isCut } = event;
253+
return <span>isCut={isCut ? 'true' : 'false'}</span>;
254+
255+
case PluginEventType.EditImage:
256+
return (
257+
<>
258+
<span>new src={event.newSrc.substr(0, 100)}</span>
259+
</>
260+
);
261+
262+
case PluginEventType.ZoomChanged:
263+
return (
264+
<span>
265+
Old value={event.oldZoomScale} New value={event.newZoomScale}
266+
</span>
267+
);
268+
269+
case PluginEventType.BeforeKeyboardEditing:
270+
return <span>Key code={event.rawEvent.which}</span>;
271+
272+
default:
273+
return null;
274+
}
275+
}
276+
277+
private clear = () => {
278+
this.events = [];
279+
this.setState({
280+
currentIndex: -1,
281+
});
282+
};
283+
284+
private renderImage = (img: HTMLImageElement, imageFile: File) => {
285+
readFile(imageFile, dataUrl => (img.src = dataUrl));
286+
};
287+
288+
private onDisplayCountChanged = () => {
289+
let value = parseInt(this.displayCount.current.value);
290+
this.setState({
291+
displayCount: value,
292+
});
293+
};
294+
295+
private renderPasteContent(
296+
title: string,
297+
content: any,
298+
renderer: (content: any) => JSX.Element = content => <span>{content}</span>
299+
): JSX.Element {
300+
return (
301+
content && (
302+
<details>
303+
<summary>{title}</summary>
304+
<div className={styles.pasteContent}>{renderer(content)}</div>
305+
</details>
306+
)
307+
);
308+
}
309+
310+
private getHtml(fragment: DocumentFragment) {
311+
const stringArray: string[] = [];
312+
for (let child = fragment.firstChild; child; child = child.nextSibling) {
313+
stringArray.push(
314+
safeInstanceOf(child, 'HTMLElement')
315+
? child.outerHTML
316+
: safeInstanceOf(child, 'Text')
317+
? child.nodeValue
318+
: ''
319+
);
320+
}
321+
322+
return stringArray.join('');
323+
}
324+
}

0 commit comments

Comments
 (0)