Skip to content

Commit

Permalink
Merge branch 'master' into u/jisong/formatHolder
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong committed Mar 28, 2024
2 parents a5db53d + d5d2542 commit ab1af1d
Show file tree
Hide file tree
Showing 25 changed files with 585 additions and 215 deletions.
8 changes: 7 additions & 1 deletion demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,13 @@ export class MainPane extends React.Component<{}, MainPaneState> {
watermarkText,
} = this.state.initState;
return [
pluginList.autoFormat && new AutoFormatPlugin(),
pluginList.autoFormat &&
new AutoFormatPlugin({
autoBullet: true,
autoNumbering: true,
autoUnlink: true,
autoLink: true,
}),
pluginList.edit && new EditPlugin(),
pluginList.paste && new PastePlugin(allowExcelNoBorderTable),
pluginList.shortcut && new ShortcutPlugin(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function createDomToModelContextForSanitizing(
document: Document,
defaultFormat?: ContentModelSegmentFormat,
defaultOption?: DomToModelOption,
additionalSanitizingOption?: DomToModelOptionForSanitizing
additionalSanitizingOption?: Partial<DomToModelOptionForSanitizing>
): DomToModelContext {
const sanitizingOption: DomToModelOptionForSanitizing = {
...DefaultSanitizingOption,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createEmptyModel, domToContentModel, parseFormat } from 'roosterjs-cont
import type {
ContentModelDocument,
ContentModelSegmentFormat,
DomToModelOption,
DomToModelOptionForSanitizing,
TrustedHTMLHandler,
} from 'roosterjs-content-model-types';

Expand All @@ -17,7 +17,7 @@ import type {
*/
export function createModelFromHtml(
html: string,
options?: DomToModelOption,
options?: Partial<DomToModelOptionForSanitizing>,
trustedHTMLHandler?: TrustedHTMLHandler,
defaultSegmentFormat?: ContentModelSegmentFormat
): ContentModelDocument {
Expand All @@ -26,7 +26,12 @@ export function createModelFromHtml(
: null;

if (doc?.body) {
const context = createDomToModelContextForSanitizing(doc, defaultSegmentFormat, options);
const context = createDomToModelContextForSanitizing(
doc,
defaultSegmentFormat,
undefined /*defaultOptions*/,
options
);
const cssRules = doc ? retrieveCssRules(doc) : [];

convertInlineCss(doc, cssRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ export const formatContentModel: FormatContentModel = (
core.api.triggerEvent(core, eventData, true /*broadcast*/);

if (canUndoByBackspace && selection?.type == 'range') {
core.undo.posContainer = selection.range.startContainer;
core.undo.posOffset = selection.range.startOffset;
core.undo.autoCompleteInsertPoint = {
node: selection.range.startContainer,
offset: selection.range.startOffset,
};
}

if (shouldAddSnapshot) {
Expand Down Expand Up @@ -128,8 +130,10 @@ function handlePendingFormat(
if (pendingFormat && selection?.type == 'range' && selection.range.collapsed) {
core.format.pendingFormat = {
format: { ...pendingFormat },
posContainer: selection.range.startContainer,
posOffset: selection.range.startOffset,
insertPoint: {
node: selection.range.startContainer,
offset: selection.range.startOffset,
},
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ class FormatPlugin implements PluginWithState<FormatPluginState> {
const selection = this.editor.getDOMSelection();
const range =
selection?.type == 'range' && selection.range.collapsed ? selection.range : null;
const { posContainer, posOffset } = this.state.pendingFormat;
const { node, offset } = this.state.pendingFormat.insertPoint;

if (range && range.startContainer == posContainer && range.startOffset == posOffset) {
if (range && range.startContainer == node && range.startOffset == offset) {
result = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ class UndoPlugin implements PluginWithState<UndoPluginState> {
snapshotsManager: createSnapshotsManager(options.snapshots),
isRestoring: false,
isNested: false,
posContainer: null,
posOffset: null,
autoCompleteInsertPoint: null,
lastKeyPress: null,
};
}
Expand Down Expand Up @@ -129,8 +128,7 @@ class UndoPlugin implements PluginWithState<UndoPluginState> {
if (evt.key == Backspace && !evt.ctrlKey && this.canUndoAutoComplete(editor)) {
evt.preventDefault();
undo(editor);
this.state.posContainer = null;
this.state.posOffset = null;
this.state.autoCompleteInsertPoint = null;
this.state.lastKeyPress = evt.key;
} else if (!evt.defaultPrevented) {
const selection = editor.getDOMSelection();
Expand Down Expand Up @@ -232,15 +230,14 @@ class UndoPlugin implements PluginWithState<UndoPluginState> {
this.state.snapshotsManager.canUndoAutoComplete() &&
selection?.type == 'range' &&
selection.range.collapsed &&
selection.range.startContainer == this.state.posContainer &&
selection.range.startOffset == this.state.posOffset
selection.range.startContainer == this.state.autoCompleteInsertPoint?.node &&
selection.range.startOffset == this.state.autoCompleteInsertPoint.offset
);
}

private addUndoSnapshot() {
this.editor?.takeSnapshot();
this.state.posContainer = null;
this.state.posOffset = null;
this.state.autoCompleteInsertPoint = null;
}

private isCtrlOrMetaPressed(editor: IEditor, event: KeyboardEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import * as convertInlineCss from '../../../lib/command/createModelFromHtml/conv
import * as createDomToModelContextForSanitizing from '../../../lib/command/createModelFromHtml/createDomToModelContextForSanitizing';
import * as domToContentModel from 'roosterjs-content-model-dom/lib/domToModel/domToContentModel';
import * as parseFormat from 'roosterjs-content-model-dom/lib/domToModel/utils/parseFormat';
import { ContentModelSegmentFormat } from 'roosterjs-content-model-types';
import { createModelFromHtml } from '../../../lib/command/createModelFromHtml/createModelFromHtml';
import {
ContentModelGeneralBlock,
ContentModelSegmentFormat,
ElementProcessor,
} from 'roosterjs-content-model-types';

describe('createModelFromHtml', () => {
it('Empty html, no options', () => {
Expand Down Expand Up @@ -134,6 +138,7 @@ describe('createModelFromHtml', () => {
expect(createContextSpy).toHaveBeenCalledWith(
mockedDoc,
mockedDefaultSegmentFormat,
undefined,
mockedOptions
);
expect(domToContentModelSpy).toHaveBeenCalledWith('BODY' as any, mockedContext);
Expand Down Expand Up @@ -202,4 +207,51 @@ describe('createModelFromHtml', () => {
expect(retrieveCssRulesSpy).not.toHaveBeenCalled();
expect(convertInlineCssSpy).not.toHaveBeenCalled();
});

it('Treat DIV with id as general block, and preserve id', () => {
const divProcessor: ElementProcessor<HTMLDivElement> = (group, element, context) => {
const processor = element.id
? context.elementProcessors['*']
: context.defaultElementProcessors.div;

processor?.(group, element, context);
};
const model = createModelFromHtml('<div id="div1">test</div>', {
processorOverride: {
div: divProcessor,
},
attributeSanitizers: {
id: true,
},
});

expect(model).toEqual({
blockGroupType: 'Document',
blocks: [
{
blockType: 'BlockGroup',
blockGroupType: 'General',
element: jasmine.anything(),
blocks: [
{
blockType: 'Paragraph',
format: {},
segments: [
{
segmentType: 'Text',
format: {},
text: 'test',
},
],
isImplicit: true,
},
],
format: {},
},
],
});
expect((model.blocks[0] as ContentModelGeneralBlock).element.outerHTML).toBe(
'<div id="div1">test</div>'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -615,8 +615,10 @@ describe('formatContentModel', () => {
it('Has pending format, callback returns true, preserve pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
Expand All @@ -626,16 +628,20 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
} as any);
});

it('Has pending format, callback returns false, preserve pending format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
Expand All @@ -645,8 +651,10 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
} as any);
});

Expand All @@ -658,8 +666,10 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

Expand All @@ -671,16 +681,20 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

it('Has pending format, callback returns true, new format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
Expand All @@ -690,16 +704,20 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

it('Has pending format, callback returns false, new format', () => {
core.format.pendingFormat = {
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

formatContentModel(core, (model, context) => {
Expand All @@ -709,16 +727,20 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat2,
posContainer: mockedStartContainer2,
posOffset: mockedStartOffset2,
insertPoint: {
node: mockedStartContainer2,
offset: mockedStartOffset2,
},
});
});

it('Has pending format, callback returns false, preserve format, selection is not collapsed', () => {
core.format.pendingFormat = {
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
};

core.api.getDOMSelection = () =>
Expand All @@ -738,8 +760,10 @@ describe('formatContentModel', () => {

expect(core.format.pendingFormat).toEqual({
format: mockedFormat1,
posContainer: mockedStartContainer1,
posOffset: mockedStartOffset1,
insertPoint: {
node: mockedStartContainer1,
offset: mockedStartOffset1,
},
});
});
});
Expand Down Expand Up @@ -841,8 +865,10 @@ describe('formatContentModel', () => {
expect(core.undo).toEqual({
isNested: false,
snapshotsManager: {},
posContainer: mockedContainer,
posOffset: mockedOffset,
autoCompleteInsertPoint: {
node: mockedContainer,
offset: mockedOffset,
},
} as any);
});

Expand Down
Loading

0 comments on commit ab1af1d

Please sign in to comment.