Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ export function formatInsertPointWithContentModel(
textWithSelection: getShadowTextProcessor(bundle),
},
tryGetFromCache: false,
// When an element carries "container level" styles such as margin or padding, we first
// wrap it in a FormatContainer. After all its child nodes are processed, we decide whether
// to keep the FormatContainer or fall back to a plain paragraph when it only wraps a single
// paragraph. However, formatInsertPointWithContentModel persists the Content Model group path
// during processing so the later formatting callback can still use it (see the
// DomToModelContextWithPath interface below). If the FormatContainer falls back to a paragraph,
// it is removed from the model and the persisted path becomes invalid. To keep the path valid,
// we skip the fallback check here and always keep the FormatContainer when one is needed.
skipFormatContainerFallbackCheck: true,
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('formatInsertPointWithContentModel', () => {
textWithSelection: jasmine.anything() as any,
},
tryGetFromCache: false,
skipFormatContainerFallbackCheck: true,
}
);

Expand Down Expand Up @@ -112,6 +113,7 @@ describe('formatInsertPointWithContentModel', () => {
textWithSelection: jasmine.anything() as any,
},
tryGetFromCache: false,
skipFormatContainerFallbackCheck: true,
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export const createContentModel: CreateContentModel = (core, option, selectionOv
? createDomToModelContext(editorContext, settings.builtIn, settings.customized, option)
: createDomToModelContextWithConfig(settings.calculated, editorContext);

if (option?.skipFormatContainerFallbackCheck) {
domToModelContext.skipFormatContainerFallbackCheck = true;
}

if (selection) {
domToModelContext.selection = selection;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ describe('createContentModel', () => {
expect(domToContentModelSpy).toHaveBeenCalledWith(mockedDiv, currentContext);
expect(model).toBe(mockedModel);
});

it('Pass skipFormatContainerFallbackCheck option to context', () => {
const currentContext = { ...originalContext } as DomToModelContext;

spyOn(createDomToModelContext, 'createDomToModelContext').and.returnValue(currentContext);

createContentModel(core, {
tryGetFromCache: false,
skipFormatContainerFallbackCheck: true,
});

expect(domToContentModelSpy).toHaveBeenCalledWith(mockedDiv, currentContext);
expect(currentContext.skipFormatContainerFallbackCheck).toBe(true);
});

it('Do not set skipFormatContainerFallbackCheck when option not passed', () => {
const currentContext = { ...originalContext } as DomToModelContext;

spyOn(createDomToModelContext, 'createDomToModelContext').and.returnValue(currentContext);

createContentModel(core, { tryGetFromCache: false });

expect(currentContext.skipFormatContainerFallbackCheck).toBeUndefined();
});
});

describe('createContentModel with selection', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function createDomToModelContext(
export function createDomToModelContextWithConfig(
config: DomToModelSettings,
editorContext?: EditorContext
) {
): DomToModelContext {
return Object.assign(
{},
editorContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ const formatContainerProcessorInternal = (
formatContainer.zeroFontSize = true;
}

if (shouldFallbackToParagraph(formatContainer) && !forceFormatContainer) {
if (
!context.skipFormatContainerFallbackCheck &&
shouldFallbackToParagraph(formatContainer) &&
!forceFormatContainer
) {
// For DIV container that only has one paragraph child, container style can be merged into paragraph
// and no need to have this container
const paragraph = formatContainer.blocks[0] as ContentModelParagraph;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,122 @@ describe('forceFormatContainerProcessor', () => {
});
});
});

describe('formatContainerProcessor with skipFormatContainerFallbackCheck', () => {
let context: DomToModelContext;

beforeEach(() => {
context = createDomToModelContext();
context.skipFormatContainerFallbackCheck = true;
});

it('div with single paragraph child should NOT fallback to paragraph', () => {
const group = createContentModelDocument();
const div = document.createElement('div');

div.appendChild(document.createTextNode('test'));

formatContainerProcessor(group, div, context);

expect(group).toEqual({
blockGroupType: 'Document',
blocks: [
{
blockType: 'BlockGroup',
blockGroupType: 'FormatContainer',
tagName: 'div',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {},
isImplicit: true,
},
],
format: {},
},
{ blockType: 'Paragraph', segments: [], format: {}, isImplicit: true },
],
});
});

it('div with id and single paragraph child should NOT fallback to paragraph', () => {
const group = createContentModelDocument();
const div = document.createElement('div');

div.id = 'testId';
div.appendChild(document.createTextNode('test'));

formatContainerProcessor(group, div, context);

expect(group).toEqual({
blockGroupType: 'Document',
blocks: [
{
blockType: 'BlockGroup',
blockGroupType: 'FormatContainer',
tagName: 'div',
blocks: [
{
blockType: 'Paragraph',
segments: [
{
segmentType: 'Text',
text: 'test',
format: {},
},
],
format: {},
isImplicit: true,
},
],
format: {
id: 'testId',
},
},
{ blockType: 'Paragraph', segments: [], format: {}, isImplicit: true },
],
});
});

it('blockquote (non-div) is unaffected and still kept as FormatContainer', () => {
const group = createContentModelDocument();
const quote = document.createElement('blockquote');

quote.appendChild(document.createTextNode('test'));

formatContainerProcessor(group, quote, context);

expect(group).toEqual({
blockGroupType: 'Document',
blocks: [
{
blockType: 'BlockGroup',
blockGroupType: 'FormatContainer',
tagName: 'blockquote',
blocks: [
{
blockType: 'Paragraph',
segments: [{ segmentType: 'Text', text: 'test', format: {} }],
format: {},
isImplicit: true,
},
],
format: {
marginTop: '1em',
marginBottom: '1em',
marginRight: '40px',
marginLeft: '40px',
},
},
{ blockType: 'Paragraph', segments: [], format: {}, isImplicit: true },
],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ export interface DomToModelOptionForCreateModel extends DomToModelOption {
* When this option is passed, "tryGetFromCache" will be ignored.
*/
recalculateTableSize?: boolean | 'all' | 'selected' | 'none';

/**
* When set to true, if a container element could be represented by a FormatContainer, always keep the
* FormatContainer and never fall back to a paragraph, even when it only has a single child.
* Set this when the intermediate FormatContainer is persisted during DOM to Content Model conversion
* and is later used during formatting.
*/
skipFormatContainerFallbackCheck?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,12 @@ export interface DomToModelSettings {
* If true elements that has display:none style will be processed
*/
processNonVisibleElements?: boolean;

/**
* When set to true, if a container element could be represented by a FormatContainer, always keep the
* FormatContainer and never fall back to a paragraph, even when it only has a single child.
* Set this when the intermediate FormatContainer is persisted during DOM to Content Model conversion
* and is later used during formatting.
*/
skipFormatContainerFallbackCheck?: boolean;
}
Loading