Skip to content

Commit b415cf1

Browse files
BryanValverdeUhaven2worldDurssjuliaroldiCopilot
authored
Bump to 9.31.0 (#3083)
* Fix toggling format issue at composition stage for CJK (#3064) * draft * temp * update ff * update * test * update * Avoid "undefined" in HTML to Markdown generated content (#3069) * fix: avoid "undefined" before paragraphs content * fix: set "image" as the default alt value instead of "undefined" --------- Co-authored-by: Francois Dursus <[email protected]> * Add focus handling in formatTableWithContentModel and its tests (#3072) * format applier (#3073) * Keep implicit paragraph when pressing Enter at the start of it. (#3075) * Enhance splitParagraph function to allow preservation of implicit paragraphs after split * Fix preserveImplicitParagraph flag in handleEnterOnParagraph to false * Update packages/roosterjs-content-model-plugins/lib/edit/utils/splitParagraph.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]> * Add beforeLogicalRootChanged event handling and related tests (#3077) * Add beforeLogicalRootChanged event handling and related tests * Rename event type from 'beforeLogicalRootChanged' to 'beforeLogicalRootChange' across relevant files * Update packages/roosterjs-content-model-types/lib/event/LogicalRootChangedEvent.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]> * Disable isComposing check in DOM event propagation for Android (#3076) * dsiable isComposing check for Android * add test --------- Co-authored-by: Bryan Valverde U <[email protected]> * Allow plugins to store state in snapshot (#3079) * Allow plugins to store custom state in undo snapshots * Type fixes * Move to using object instead array * Update main version to 9.31.0 in versions.json --------- Co-authored-by: Haowen Chen <[email protected]> Co-authored-by: François Dursus <[email protected]> Co-authored-by: Francois Dursus <[email protected]> Co-authored-by: Julia Roldi <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Rain-Zheng <[email protected]> Co-authored-by: florian-msft <[email protected]>
1 parent c038649 commit b415cf1

File tree

14 files changed

+283
-6
lines changed

14 files changed

+283
-6
lines changed

demo/scripts/controlsV2/sidePane/snapshot/SnapshotPane.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface SnapshotPaneState {
1717

1818
export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPaneState> {
1919
private html = React.createRef<HTMLTextAreaElement>();
20+
private additionalState = React.createRef<HTMLTextAreaElement>();
2021
private entityStates = React.createRef<HTMLTextAreaElement>();
2122
private isDarkColor = React.createRef<HTMLInputElement>();
2223
private selection = React.createRef<HTMLTextAreaElement>();
@@ -49,6 +50,12 @@ export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPan
4950
<textarea ref={this.html} className={styles.textarea} spellCheck={false} />
5051
<div>Selection:</div>
5152
<textarea ref={this.selection} className={styles.textarea} spellCheck={false} />
53+
<div>Additional state:</div>
54+
<textarea
55+
ref={this.additionalState}
56+
className={styles.textarea}
57+
spellCheck={false}
58+
/>
5259
<div>Entity states:</div>
5360
<textarea ref={this.entityStates} className={styles.textarea} spellCheck={false} />
5461
<div>Logical root path:</div>
@@ -66,6 +73,9 @@ export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPan
6673
const selection = this.selection.current.value
6774
? (JSON.parse(this.selection.current.value) as SnapshotSelection)
6875
: undefined;
76+
const additionalState = this.additionalState.current.value
77+
? JSON.parse(this.additionalState.current.value)
78+
: undefined;
6979
const entityStates = this.entityStates.current.value
7080
? (JSON.parse(this.entityStates.current.value) as EntityState[])
7181
: undefined;
@@ -76,6 +86,7 @@ export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPan
7686

7787
return {
7888
html,
89+
additionalState,
7990
entityStates,
8091
isDarkMode,
8192
selection,
@@ -121,6 +132,7 @@ export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPan
121132

122133
this.setSnapshot({
123134
html: html,
135+
additionalState: {},
124136
entityStates: [],
125137
isDarkMode,
126138
selection: metadata as SnapshotSelection,
@@ -155,6 +167,9 @@ export class SnapshotPane extends React.Component<SnapshotPaneProps, SnapshotPan
155167

156168
private setSnapshot = (snapshot: Snapshot) => {
157169
this.html.current.value = snapshot.html;
170+
this.additionalState.current.value = snapshot.additionalState
171+
? JSON.stringify(snapshot.additionalState)
172+
: '';
158173
this.entityStates.current.value = snapshot.entityStates
159174
? JSON.stringify(snapshot.entityStates)
160175
: '';

packages/roosterjs-content-model-core/lib/coreApi/addUndoSnapshot/addUndoSnapshot.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
AddUndoSnapshot,
44
EntityOperationEvent,
55
Snapshot,
6+
BeforeAddUndoSnapshotEvent,
67
} from 'roosterjs-content-model-types';
78
import { createSnapshotSelection } from './createSnapshotSelection';
89
import { getPath } from './getPath';
@@ -21,6 +22,13 @@ export const addUndoSnapshot: AddUndoSnapshot = (core, canUndoByBackspace, entit
2122
let snapshot: Snapshot | null = null;
2223

2324
if (!lifecycle.shadowEditFragment) {
25+
// Give plugins the chance to add additional state to the snapshot
26+
const beforeAddUndoSnapshotEvent: BeforeAddUndoSnapshotEvent = {
27+
eventType: 'beforeAddUndoSnapshot',
28+
additionalState: {},
29+
};
30+
core.api.triggerEvent(core, beforeAddUndoSnapshotEvent, false);
31+
2432
// Need to create snapshot selection before retrieve innerHTML since HTML can be changed during creating selection when normalize table
2533
const selection = createSnapshotSelection(core);
2634
const html = physicalRoot.innerHTML;
@@ -61,6 +69,7 @@ export const addUndoSnapshot: AddUndoSnapshot = (core, canUndoByBackspace, entit
6169

6270
snapshot = {
6371
html,
72+
additionalState: beforeAddUndoSnapshotEvent.additionalState,
6473
entityStates,
6574
isDarkMode: !!lifecycle.isDarkMode,
6675
selection,

packages/roosterjs-content-model-core/lib/coreApi/restoreUndoSnapshot/restoreUndoSnapshot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const restoreUndoSnapshot: RestoreUndoSnapshot = (core, snapshot) => {
3131

3232
const event: ContentChangedEvent = {
3333
eventType: 'contentChanged',
34+
additionalState: snapshot.additionalState,
3435
entityStates: snapshot.entityStates,
3536
source: ChangeSource.SetContent,
3637
};

packages/roosterjs-content-model-core/lib/corePlugin/undo/SnapshotsManagerImpl.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class SnapshotsManagerImpl implements SnapshotsManager {
5050
const isSameSnapshot =
5151
currentSnapshot &&
5252
currentSnapshot.html == snapshot.html &&
53+
!currentSnapshot.additionalState &&
54+
!snapshot.additionalState &&
5355
!currentSnapshot.entityStates &&
5456
!snapshot.entityStates;
5557
const addSnapshot = !currentSnapshot || shouldAddSnapshot(currentSnapshot, snapshot);
@@ -134,6 +136,11 @@ export function createSnapshotsManager(snapshots?: Snapshots): SnapshotsManager
134136
function shouldAddSnapshot(currentSnapshot: Snapshot, snapshot: Snapshot) {
135137
return (
136138
currentSnapshot.html !== snapshot.html ||
139+
(currentSnapshot.additionalState &&
140+
snapshot.additionalState &&
141+
JSON.stringify(currentSnapshot.additionalState) !==
142+
JSON.stringify(snapshot.additionalState)) ||
143+
(!currentSnapshot.additionalState && snapshot.additionalState) ||
137144
(currentSnapshot.entityStates &&
138145
snapshot.entityStates &&
139146
currentSnapshot.entityStates !== snapshot.entityStates) ||

packages/roosterjs-content-model-core/test/coreApi/addUndoSnapshot/addUndoSnapshotTest.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,24 @@ describe('addUndoSnapshot', () => {
7777
expect(addSnapshotSpy).toHaveBeenCalledWith(
7878
{
7979
html: mockedHTML,
80+
additionalState: {},
8081
entityStates: undefined,
8182
isDarkMode: false,
8283
selection: mockedSnapshotSelection,
8384
},
8485
false
8586
);
86-
expect(triggerEventSpy).not.toHaveBeenCalled();
87+
expect(triggerEventSpy).toHaveBeenCalledWith(
88+
core,
89+
{
90+
eventType: 'beforeAddUndoSnapshot',
91+
additionalState: {},
92+
},
93+
false
94+
);
8795
expect(result).toEqual({
8896
html: mockedHTML,
97+
additionalState: {},
8998
entityStates: undefined,
9099
isDarkMode: false,
91100
selection: mockedSnapshotSelection,
@@ -112,15 +121,24 @@ describe('addUndoSnapshot', () => {
112121
expect(addSnapshotSpy).toHaveBeenCalledWith(
113122
{
114123
html: mockedHTML,
124+
additionalState: {},
115125
entityStates: undefined,
116126
isDarkMode: false,
117127
selection: mockedSnapshotSelection,
118128
},
119129
true
120130
);
121-
expect(triggerEventSpy).not.toHaveBeenCalled();
131+
expect(triggerEventSpy).toHaveBeenCalledWith(
132+
core,
133+
{
134+
eventType: 'beforeAddUndoSnapshot',
135+
additionalState: {},
136+
},
137+
false
138+
);
122139
expect(result).toEqual({
123140
html: mockedHTML,
141+
additionalState: {},
124142
entityStates: undefined,
125143
isDarkMode: false,
126144
selection: mockedSnapshotSelection,
@@ -148,6 +166,7 @@ describe('addUndoSnapshot', () => {
148166
expect(addSnapshotSpy).toHaveBeenCalledWith(
149167
{
150168
html: mockedHTML,
169+
additionalState: {},
151170
entityStates: mockedEntityStates,
152171
isDarkMode: false,
153172
selection: mockedSnapshotSelection,
@@ -156,12 +175,63 @@ describe('addUndoSnapshot', () => {
156175
);
157176
expect(result).toEqual({
158177
html: mockedHTML,
178+
additionalState: {},
159179
entityStates: mockedEntityStates,
160180
isDarkMode: false,
161181
selection: mockedSnapshotSelection,
162182
});
163183
});
164184

185+
it('Has additional state', () => {
186+
const mockedColors = 'COLORS' as any;
187+
const mockedHTML = 'HTML' as any;
188+
const mockedSnapshotSelection = 'SNAPSHOTSELECTION' as any;
189+
const mockedAdditionalState = { state: 'custom' };
190+
191+
contentDiv.innerHTML = mockedHTML;
192+
193+
getKnownColorsCopySpy.and.returnValue(mockedColors);
194+
createSnapshotSelectionSpy.and.returnValue(mockedSnapshotSelection);
195+
triggerEventSpy.and.callFake((core, event, broadcast) => {
196+
if (event.eventType === 'beforeAddUndoSnapshot') {
197+
event.additionalState = mockedAdditionalState;
198+
}
199+
});
200+
201+
const result = addUndoSnapshot(core, false);
202+
203+
expect(core.undo).toEqual({
204+
snapshotsManager: snapshotsManager,
205+
} as any);
206+
expect(snapshotsManager.hasNewContent).toBeFalse();
207+
expect(createSnapshotSelectionSpy).toHaveBeenCalledWith(core);
208+
expect(addSnapshotSpy).toHaveBeenCalledWith(
209+
{
210+
html: mockedHTML,
211+
additionalState: mockedAdditionalState,
212+
entityStates: undefined,
213+
isDarkMode: false,
214+
selection: mockedSnapshotSelection,
215+
},
216+
false
217+
);
218+
expect(triggerEventSpy).toHaveBeenCalledWith(
219+
core,
220+
{
221+
eventType: 'beforeAddUndoSnapshot',
222+
additionalState: mockedAdditionalState,
223+
},
224+
false
225+
);
226+
expect(result).toEqual({
227+
html: mockedHTML,
228+
additionalState: mockedAdditionalState,
229+
entityStates: undefined,
230+
isDarkMode: false,
231+
selection: mockedSnapshotSelection,
232+
});
233+
});
234+
165235
it('Verify get html after create selection', () => {
166236
const mockedColors = 'COLORS' as any;
167237
const mockedHTML1 = 'HTML1' as any;
@@ -186,15 +256,24 @@ describe('addUndoSnapshot', () => {
186256
expect(addSnapshotSpy).toHaveBeenCalledWith(
187257
{
188258
html: mockedHTML2,
259+
additionalState: {},
189260
entityStates: undefined,
190261
isDarkMode: false,
191262
selection: mockedSnapshotSelection,
192263
},
193264
false
194265
);
195-
expect(triggerEventSpy).not.toHaveBeenCalled();
266+
expect(triggerEventSpy).toHaveBeenCalledWith(
267+
core,
268+
{
269+
eventType: 'beforeAddUndoSnapshot',
270+
additionalState: {},
271+
},
272+
false
273+
);
196274
expect(result).toEqual({
197275
html: mockedHTML2,
276+
additionalState: {},
198277
entityStates: undefined,
199278
isDarkMode: false,
200279
selection: mockedSnapshotSelection,
@@ -225,6 +304,7 @@ describe('addUndoSnapshot', () => {
225304
expect(addSnapshotSpy).toHaveBeenCalledWith(
226305
{
227306
html: mockedHTML,
307+
additionalState: {},
228308
entityStates: mockedEntityStates,
229309
isDarkMode: false,
230310
selection: mockedSnapshotSelection,
@@ -234,6 +314,7 @@ describe('addUndoSnapshot', () => {
234314
);
235315
expect(result).toEqual({
236316
html: mockedHTML,
317+
additionalState: {},
237318
entityStates: mockedEntityStates,
238319
isDarkMode: false,
239320
selection: mockedSnapshotSelection,

packages/roosterjs-content-model-core/test/coreApi/restoreUndoSnapshot/restoreUndoSnapshotTest.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe('restoreUndoSnapshot', () => {
5555
core,
5656
{
5757
eventType: 'contentChanged',
58+
additionalState: undefined,
5859
entityStates: undefined,
5960
source: ChangeSource.SetContent,
6061
},
@@ -89,6 +90,7 @@ describe('restoreUndoSnapshot', () => {
8990
core,
9091
{
9192
eventType: 'contentChanged',
93+
additionalState: undefined,
9294
entityStates: mockedEntityState,
9395
source: ChangeSource.SetContent,
9496
},
@@ -99,4 +101,39 @@ describe('restoreUndoSnapshot', () => {
99101
expect(restoreSnapshotHTMLSpy).toHaveBeenCalledWith(core, snapshot);
100102
expect(restoreSnapshotSelectionSpy).toHaveBeenCalledWith(core, snapshot);
101103
});
104+
105+
it('restore snapshot, with additional state', () => {
106+
const mockedHTML = 'HTML' as any;
107+
const mockedAdditionalState = { state: 'custom' } as any;
108+
const snapshot: Snapshot = {
109+
html: mockedHTML,
110+
additionalState: mockedAdditionalState,
111+
} as any;
112+
113+
restoreUndoSnapshot(core, snapshot);
114+
115+
expect(triggerEventSpy).toHaveBeenCalledTimes(2);
116+
expect(triggerEventSpy).toHaveBeenCalledWith(
117+
core,
118+
{
119+
eventType: 'beforeSetContent',
120+
newContent: mockedHTML,
121+
},
122+
true
123+
);
124+
expect(triggerEventSpy).toHaveBeenCalledWith(
125+
core,
126+
{
127+
eventType: 'contentChanged',
128+
additionalState: mockedAdditionalState,
129+
entityStates: undefined,
130+
source: ChangeSource.SetContent,
131+
},
132+
false
133+
);
134+
expect(setLogicalRootSpy).toHaveBeenCalledWith(core, null);
135+
expect(restoreSnapshotColorsSpy).toHaveBeenCalledWith(core, snapshot);
136+
expect(restoreSnapshotHTMLSpy).toHaveBeenCalledWith(core, snapshot);
137+
expect(restoreSnapshotSelectionSpy).toHaveBeenCalledWith(core, snapshot);
138+
});
102139
});

0 commit comments

Comments
 (0)