Skip to content

Commit

Permalink
0905
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong committed Sep 5, 2023
1 parent 5366ab8 commit 00bf196
Show file tree
Hide file tree
Showing 64 changed files with 926 additions and 645 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defaultProcessorMap } from './defaultProcessors';
import { ElementProcessorMap } from 'roosterjs-content-model-types';

/**
* Build a DOM processor map with overrides that can be used as base processor map
* @param processorOverrides DOM processor overrides to default processors.
* Note: Inside an override processor it cannot call original processor using context.defaultElementProcessors.<ProcessorName>
* since here the default processor is also overridden
*/
export function buildBaseProcessorMap(
...processorOverrides: (Partial<ElementProcessorMap> | undefined)[]
): ElementProcessorMap {
return Object.assign({}, defaultProcessorMap, ...processorOverrides);
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,82 @@
import { defaultProcessorMap } from './defaultProcessors';
import { DomToModelContext, DomToModelOption, EditorContext } from 'roosterjs-content-model-types';
import { getFormatParsers } from '../../formatHandlers/defaultFormatHandlers';
import { getObjectKeys } from 'roosterjs-editor-dom';
import { SelectionRangeEx } from 'roosterjs-editor-types';
import {
defaultFormatHandlerMap,
defaultFormatKeysPerCategory,
} from '../../formatHandlers/defaultFormatHandlers';
import {
ContentModelBlockFormat,
DomToModelContext,
DomToModelDecoratorContext,
DomToModelFormatContext,
DomToModelSelectionContext,
DomToModelSettings,
EditorContext,
ElementProcessorMap,
FormatParser,
FormatParsers,
FormatParsersPerCategory,
} from 'roosterjs-content-model-types';

/**
* Create context object form DOM to Content Model conversion
* Create context object for DOM to Content Model conversion
* @param processorOverride Overrides default element processors
* @param formatParserOverride Overrides default format handlers
* @param additionalFormatParsers: Provide additional format parsers for each format type
* @param baseProcessorMap Base DOM processor map, if not passed, default processor map will be used
* @param editorContext Context of editor
* @param options Options for this context
* @param selection Selection that already exists in content
*/
export function createDomToModelContext(
editorContext?: EditorContext,
options?: DomToModelOption,
selection?: SelectionRangeEx
processorOverride?: Partial<ElementProcessorMap>,
formatParserOverride?: Partial<FormatParsers>,
additionalFormatParsers?: (Partial<FormatParsersPerCategory> | undefined)[],
baseProcessorMap?: Readonly<ElementProcessorMap>,
selection?: SelectionRangeEx,
editorContext?: EditorContext
): DomToModelContext {
const context: DomToModelContext = {
...editorContext,
return Object.assign(
{},
editorContext,
createDomToModelSelectionContext(selection),
createDomToModelFormatContext(editorContext?.isRootRtl),
createDomToModelDecoratorContext(),
createDomToModelSettings(
processorOverride,
formatParserOverride,
additionalFormatParsers,
baseProcessorMap
)
);
}

function createDomToModelSelectionContext(rangeEx?: SelectionRangeEx): DomToModelSelectionContext {
const result: DomToModelSelectionContext = { isInSelection: false };

if (rangeEx) {
result.rangeEx = rangeEx;
}

return result;
}

blockFormat: {},
function createDomToModelFormatContext(isRootRtl?: boolean): DomToModelFormatContext {
const blockFormat: ContentModelBlockFormat = isRootRtl ? { direction: 'rtl' } : {};

return {
blockFormat,
segmentFormat: {},
isInSelection: false,

listFormat: {
levels: [],
threadItemCounts: [],
},
};
}

function createDomToModelDecoratorContext(): DomToModelDecoratorContext {
return {
link: {
format: {},
dataset: {},
Expand All @@ -36,27 +88,65 @@ export function createDomToModelContext(
format: {},
tagName: '',
},
};
}

elementProcessors: {
...defaultProcessorMap,
...(options?.processorOverride || {}),
},

formatParsers: getFormatParsers(
options?.formatParserOverride,
options?.additionalFormatParsers
),
function createDomToModelSettings(
processorOverride?: Partial<ElementProcessorMap>,
formatParserOverride?: Partial<FormatParsers>,
additionalFormatParsers?: (Partial<FormatParsersPerCategory> | undefined)[],
baseProcessorMap?: Readonly<ElementProcessorMap>
): DomToModelSettings {
const defaultElementProcessors = baseProcessorMap ?? defaultProcessorMap;

defaultElementProcessors: defaultProcessorMap,
return {
elementProcessors: processorOverride
? { ...defaultElementProcessors, ...processorOverride }
: defaultElementProcessors,
formatParsers:
formatParserOverride || (additionalFormatParsers?.length ?? 0) > 0
? buildFormatParsers(formatParserOverride, additionalFormatParsers)
: defaultFormatParsersPerCategory,
defaultElementProcessors,
};
}

if (editorContext?.isRootRtl) {
context.blockFormat.direction = 'rtl';
}
const defaultFormatParsers: Readonly<FormatParsers> = getObjectKeys(defaultFormatHandlerMap).reduce(
(result, key) => {
result[key] = defaultFormatHandlerMap[key].parse as FormatParser<any>;
return result;
},
<FormatParsers>{}
);

if (selection) {
context.rangeEx = selection;
}
/**
* Build format parsers used by DOM to Content Model conversion
* @param override
* @param additionalParsersArray
* @returns
*/
function buildFormatParsers(
override: Partial<FormatParsers> = {},
additionalParsersArray: (Partial<FormatParsersPerCategory> | undefined)[] = []
): FormatParsersPerCategory {
return getObjectKeys(defaultFormatKeysPerCategory).reduce((result, key) => {
const value = defaultFormatKeysPerCategory[key]
.map(
formatKey =>
(override[formatKey] === undefined
? defaultFormatParsers[formatKey]
: override[formatKey]) as FormatParser<any>
)
.concat(
...additionalParsersArray.map(
parsers => (parsers?.[key] ?? []) as FormatParser<any>[]
)
);

return context;
result[key] = value;

return result;
}, {} as FormatParsersPerCategory);
}

const defaultFormatParsersPerCategory = buildFormatParsers();
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
import { createContentModelDocument } from '../modelApi/creators/createContentModelDocument';
import { createDomToModelContext } from './context/createDomToModelContext';
import { normalizeContentModel } from '../modelApi/common/normalizeContentModel';
import { SelectionRangeEx } from 'roosterjs-editor-types';
import {
ContentModelDocument,
DomToModelOption,
EditorContext,
} from 'roosterjs-content-model-types';
import { ContentModelDocument, DomToModelContext } from 'roosterjs-content-model-types';

/**
* Create Content Model from DOM tree in this editor
* @param root Root element of DOM tree to create Content Model from
* @param option The option to customize the behavior of DOM to Content Model conversion
* @param editorContext Context of content model editor
* @param selection Existing selection range in editor
* @param config DOM Processor and format parser configuration
* @param editorContext Context of editor
* @param selection Selection that already exists in content
* @returns A ContentModelDocument object that contains all the models created from the give root element
*/
export function domToContentModel(
root: HTMLElement | DocumentFragment,
option?: DomToModelOption,
editorContext?: EditorContext,
selection?: SelectionRangeEx
context: DomToModelContext
): ContentModelDocument {
const model = createContentModelDocument(editorContext?.defaultFormat);
const context = createDomToModelContext(editorContext, option, selection);
const model = createContentModelDocument(context.defaultFormat);

context.elementProcessors.child(model, root, context);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { floatFormatHandler } from './common/floatFormatHandler';
import { fontFamilyFormatHandler } from './segment/fontFamilyFormatHandler';
import { fontSizeFormatHandler } from './segment/fontSizeFormatHandler';
import { FormatHandler } from './FormatHandler';
import { getObjectKeys } from 'roosterjs-editor-dom';
import { htmlAlignFormatHandler } from './block/htmlAlignFormatHandler';
import { idFormatHandler } from './common/idFormatHandler';
import { italicFormatHandler } from './segment/italicFormatHandler';
Expand Down Expand Up @@ -38,19 +37,16 @@ import {
ContentModelFormatMap,
FormatHandlerTypeMap,
FormatKey,
FormatApplier,
FormatAppliers,
FormatAppliersPerCategory,
FormatParser,
FormatParsers,
FormatParsersPerCategory,
} from 'roosterjs-content-model-types';

type FormatHandlers = {
[Key in FormatKey]: FormatHandler<FormatHandlerTypeMap[Key]>;
};

const defaultFormatHandlerMap: FormatHandlers = {
/**
* @internal
*/
export const defaultFormatHandlerMap: FormatHandlers = {
backgroundColor: backgroundColorFormatHandler,
bold: boldFormatHandler,
border: borderFormatHandler,
Expand Down Expand Up @@ -113,7 +109,10 @@ const sharedContainerFormats: (keyof FormatHandlerTypeMap)[] = [
'border',
];

const defaultFormatKeysPerCategory: {
/**
* @internal
*/
export const defaultFormatKeysPerCategory: {
[key in keyof ContentModelFormatMap]: (keyof FormatHandlerTypeMap)[];
} = {
block: sharedBlockFormats,
Expand Down Expand Up @@ -196,65 +195,3 @@ const defaultFormatKeysPerCategory: {
divider: [...sharedBlockFormats, ...sharedContainerFormats, 'display', 'size', 'htmlAlign'],
container: [...sharedContainerFormats, 'htmlAlign', 'size', 'display'],
};

const defaultFormatParsers: FormatParsers = getObjectKeys(defaultFormatHandlerMap).reduce(
(result, key) => {
result[key] = defaultFormatHandlerMap[key].parse as FormatParser<any>;
return result;
},
<FormatParsers>{}
);

const defaultFormatAppliers: FormatAppliers = getObjectKeys(defaultFormatHandlerMap).reduce(
(result, key) => {
result[key] = defaultFormatHandlerMap[key].apply as FormatApplier<any>;
return result;
},
<FormatAppliers>{}
);

/**
* @internal
*/
export function getFormatParsers(
override: Partial<FormatParsers> = {},
additionalParsers: Partial<FormatParsersPerCategory> = {}
): FormatParsersPerCategory {
return getObjectKeys(defaultFormatKeysPerCategory).reduce((result, key) => {
const value = defaultFormatKeysPerCategory[key]
.map(
formatKey =>
(override[formatKey] === undefined
? defaultFormatParsers[formatKey]
: override[formatKey]) as FormatParser<any>
)
.concat((additionalParsers[key] as FormatParser<any>[]) || []);

result[key] = value;

return result;
}, {} as FormatParsersPerCategory);
}

/**
* @internal
*/
export function getFormatAppliers(
override: Partial<FormatAppliers> = {},
additionalAppliers: Partial<FormatAppliersPerCategory> = {}
): FormatAppliersPerCategory {
return getObjectKeys(defaultFormatKeysPerCategory).reduce((result, key) => {
const value = defaultFormatKeysPerCategory[key]
.map(
formatKey =>
(override[formatKey] === undefined
? defaultFormatAppliers[formatKey]
: override[formatKey]) as FormatApplier<any>
)
.concat((additionalAppliers[key] as FormatApplier<any>[]) || []);

result[key] = value;

return result;
}, {} as FormatAppliersPerCategory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ export { BorderKeys } from './formatHandlers/common/borderFormatHandler';
export { DeprecatedColors } from './formatHandlers/utils/color';

export { createDomToModelContext } from './domToModel/context/createDomToModelContext';
export { createModelToDomContext } from './modelToDom/context/createModelToDomContext';
export { buildBaseProcessorMap } from './domToModel/context/buildBaseProcessorMap';
export { buildBaseHandlerMap } from './modelToDom/context/buildBaseHandlerMap';
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { createModelToDomContext } from './context/createModelToDomContext';
import { createRange, Position, toArray } from 'roosterjs-editor-dom';
import { isNodeOfType } from '../domUtils/isNodeOfType';
import {
ContentModelDocument,
EditorContext,
ModelToDomBlockAndSegmentNode,
ModelToDomContext,
ModelToDomOption,
} from 'roosterjs-content-model-types';
import {
NodePosition,
Expand All @@ -22,8 +19,8 @@ import {
* When a DOM node with existing node is passed, it will be merged with content model so that unchanged blocks
* won't be touched.
* @param model The content model document to generate DOM tree from
* @param config DOM handler and format applier configuration
* @param editorContext Content for Content Model editor
* @param option Additional options to customize the behavior of Content Model to DOM conversion
* @returns A tuple of the following 3 objects:
* 1. Document Fragment that contains the DOM tree generated from the given model
* 2. A SelectionRangeEx object that contains selection info from the model if any, or null
Expand All @@ -33,14 +30,11 @@ export function contentModelToDom(
doc: Document,
root: Node,
model: ContentModelDocument,
editorContext?: EditorContext,
option?: ModelToDomOption
context: ModelToDomContext
): SelectionRangeEx | null {
const modelToDomContext = createModelToDomContext(editorContext, option);
context.modelHandlers.blockGroupChildren(doc, root, model, context);

modelToDomContext.modelHandlers.blockGroupChildren(doc, root, model, modelToDomContext);

const range = extractSelectionRange(modelToDomContext);
const range = extractSelectionRange(context);

root.normalize();

Expand Down
Loading

0 comments on commit 00bf196

Please sign in to comment.