Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Model customization step 3: Customize format handlers #1255

Merged
merged 15 commits into from
Sep 15, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defaultStyleMap } from './defaultStyles';
import { DomToModelContext } from '../../publicTypes/context/DomToModelContext';
import { DomToModelOption } from '../../publicTypes/IExperimentalContentModelEditor';
import { EditorContext } from '../../publicTypes/context/EditorContext';
import { getFormatParsers } from '../../formatHandlers/defaultFormatHandlers';
import { SelectionRangeEx, SelectionRangeTypes } from 'roosterjs-editor-types';

/**
Expand Down Expand Up @@ -33,6 +34,8 @@ export function createDomToModelContext(
...defaultStyleMap,
...(options?.defaultStyleOverride || {}),
},

formatParsers: getFormatParsers(options?.formatParserOverride),
};

switch (range?.type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { ContentModelFormatBase } from '../../publicTypes/format/ContentModelFormatBase';
import { defaultStyleMap } from '../context/defaultStyles';
import { DomToModelContext } from '../../publicTypes/context/DomToModelContext';
import { FormatHandler } from '../../formatHandlers/FormatHandler';
import { FormatKey } from '../../publicTypes/format/FormatHandlerTypeMap';

/**
* @internal
*/
export function parseFormat<T extends ContentModelFormatBase>(
element: HTMLElement,
handlers: FormatHandler<T>[],
handlerKeys: FormatKey[],
format: T,
context: DomToModelContext
) {
const styleItem = defaultStyleMap[element.tagName];
const styleItem = context.defaultStyles[element.tagName];
const defaultStyle = styleItem || {};

handlers.forEach(handler => {
handler.parse(format, element, context, defaultStyle);
handlerKeys.forEach(key => {
context.formatParsers[key](format, element, context, defaultStyle);
});
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import { ContentModelFormatBase } from '../publicTypes/format/ContentModelFormatBase';
import { DomToModelContext } from '../publicTypes/context/DomToModelContext';
import { ModelToDomContext } from '../publicTypes/context/ModelToDomContext';
import { FormatApplier } from '../publicTypes/context/ModelToDomSettings';
import { FormatParser } from '../publicTypes/context/DomToModelSettings';

/**
* @internal
* Represents an object that will handle a given format
*/
export interface FormatHandler<TFormat extends ContentModelFormatBase> {
/**
* Parse format from the given HTML element and default style
* @param format The format object to parse into
* @param element The HTML element to parse format from
* @param context The context object that provide related context information
* @param defaultStyle Default CSS style of the given HTML element
*/
parse: (
format: TFormat,
element: HTMLElement,
context: DomToModelContext,
defaultStyle: Readonly<Partial<CSSStyleDeclaration>>
) => void;
parse: FormatParser<TFormat>;

/**
* Apply format to the given HTML element
* @param format The format object to apply
* @param element The HTML element to apply format to
* @param context The context object that provide related context information
*/
apply: (format: TFormat, element: HTMLElement, context: ModelToDomContext) => void;
apply: FormatApplier<TFormat>;
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
import { backgroundColorFormatHandler } from './common/backgroundColorFormatHandler';
import { boldFormatHandler } from './segment/boldFormatHandler';
import { ContentModelSegmentFormat } from '../publicTypes/format/ContentModelSegmentFormat';
import { fontFamilyFormatHandler } from './segment/fontFamilyFormatHandler';
import { fontSizeFormatHandler } from './segment/fontSizeFormatHandler';
import { FormatHandler } from './FormatHandler';
import { italicFormatHandler } from './segment/italicFormatHandler';
import { strikeFormatHandler } from './segment/strikeFormatHandler';
import { superOrSubScriptFormatHandler } from './segment/superOrSubScriptFormatHandler';
import { textColorFormatHandler } from './segment/textColorFormatHandler';
import { underlineFormatHandler } from './segment/underlineFormatHandler';
import { FormatKey } from '../publicTypes/format/FormatHandlerTypeMap';

/**
* @internal
* Order by frequency, from not common used to common used, for better optimization
*/
export const SegmentFormatHandlers: FormatHandler<ContentModelSegmentFormat>[] = [
superOrSubScriptFormatHandler,
strikeFormatHandler,
fontFamilyFormatHandler,
fontSizeFormatHandler,
underlineFormatHandler,
italicFormatHandler,
boldFormatHandler,
textColorFormatHandler,
backgroundColorFormatHandler,
export const SegmentFormatHandlers: FormatKey[] = [
'superOrSubScript',
'strike',
'fontFamily',
'fontSize',
'underline',
'italic',
'bold',
'textColor',
'backgroundColor',
];
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { backgroundColorFormatHandler } from './common/backgroundColorFormatHandler';
import { borderFormatHandler } from './common/borderFormatHandler';
import { ContentModelTableCellFormat } from '../publicTypes/format/ContentModelTableCellFormat';
import { FormatHandler } from './FormatHandler';
import { tableCellMetadataFormatHandler } from './table/tableCellMetadataFormatHandler';
import { textAlignFormatHandler } from './common/textAlignFormatHandler';
import { verticalAlignFormatHandler } from './common/verticalAlignFormatHandler';
import { FormatKey } from '../publicTypes/format/FormatHandlerTypeMap';

/**
* @internal
*/
export const TableCellFormatHandlers: FormatHandler<ContentModelTableCellFormat>[] = [
borderFormatHandler,
backgroundColorFormatHandler,
textAlignFormatHandler,
verticalAlignFormatHandler,
tableCellMetadataFormatHandler,
export const TableCellFormatHandlers: FormatKey[] = [
'border',
'backgroundColor',
'textAlign',
'verticalAlign',
'tableCellMetadata',
];
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { backgroundColorFormatHandler } from './common/backgroundColorFormatHandler';
import { borderFormatHandler } from './common/borderFormatHandler';
import { ContentModelTableFormat } from '../publicTypes/format/ContentModelTableFormat';
import { FormatHandler } from './FormatHandler';
import { idFormatHandler } from './common/idFormatHandler';
import { marginFormatHandler } from './paragraph/marginFormatHandler';
import { tableMetadataFormatHandler } from './table/tableMetadataFormatHandler';
import { tableSpacingFormatHandler } from './table/tableSpacingFormatHandler';
import { FormatKey } from '../publicTypes/format/FormatHandlerTypeMap';

/**
* @internal
*/
export const TableFormatHandlers: FormatHandler<ContentModelTableFormat>[] = [
idFormatHandler,
borderFormatHandler,
tableMetadataFormatHandler,
tableSpacingFormatHandler,
marginFormatHandler,
backgroundColorFormatHandler,
export const TableFormatHandlers: FormatKey[] = [
'id',
'border',
'tableMetadata',
'tableSpacing',
'margin',
'backgroundColor',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { backgroundColorFormatHandler } from './common/backgroundColorFormatHandler';
import { boldFormatHandler } from './segment/boldFormatHandler';
import { borderFormatHandler } from './common/borderFormatHandler';
import { fontFamilyFormatHandler } from './segment/fontFamilyFormatHandler';
import { fontSizeFormatHandler } from './segment/fontSizeFormatHandler';
import { FormatAppliers } from '../publicTypes/context/ModelToDomSettings';
import { FormatHandler } from './FormatHandler';
import { FormatHandlerTypeMap, FormatKey } from '../publicTypes/format/FormatHandlerTypeMap';
import { FormatParsers } from '../publicTypes/context/DomToModelSettings';
import { getObjectKeys } from 'roosterjs-editor-dom';
import { idFormatHandler } from './common/idFormatHandler';
import { italicFormatHandler } from './segment/italicFormatHandler';
import { marginFormatHandler } from './paragraph/marginFormatHandler';
import { strikeFormatHandler } from './segment/strikeFormatHandler';
import { superOrSubScriptFormatHandler } from './segment/superOrSubScriptFormatHandler';
import { tableCellMetadataFormatHandler } from './table/tableCellMetadataFormatHandler';
import { tableMetadataFormatHandler } from './table/tableMetadataFormatHandler';
import { tableSpacingFormatHandler } from './table/tableSpacingFormatHandler';
import { textAlignFormatHandler } from './common/textAlignFormatHandler';
import { textColorFormatHandler } from './segment/textColorFormatHandler';
import { underlineFormatHandler } from './segment/underlineFormatHandler';
import { verticalAlignFormatHandler } from './common/verticalAlignFormatHandler';

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

const defaultFormatHandlerMap: FormatHandlers = {
backgroundColor: backgroundColorFormatHandler,
bold: boldFormatHandler,
border: borderFormatHandler,
fontFamily: fontFamilyFormatHandler,
fontSize: fontSizeFormatHandler,
id: idFormatHandler,
italic: italicFormatHandler,
margin: marginFormatHandler,
strike: strikeFormatHandler,
superOrSubScript: superOrSubScriptFormatHandler,
tableCellMetadata: tableCellMetadataFormatHandler,
tableMetadata: tableMetadataFormatHandler,
tableSpacing: tableSpacingFormatHandler,
textAlign: textAlignFormatHandler,
textColor: textColorFormatHandler,
underline: underlineFormatHandler,
verticalAlign: verticalAlignFormatHandler,
};

/**
* @internal
*/
export function getFormatParsers(option?: Partial<FormatParsers>): FormatParsers {
return getObjectKeys(defaultFormatHandlerMap).reduce((parsers, key) => {
parsers[key] = option?.[key] || defaultFormatHandlerMap[key].parse;

return parsers;
}, <FormatParsers>{});
}

/**
* @internal
*/
export function getFormatAppliers(option?: Partial<FormatAppliers>): FormatAppliers {
return getObjectKeys(defaultFormatHandlerMap).reduce((parsers, key) => {
parsers[key] = option?.[key] || defaultFormatHandlerMap[key].apply;

return parsers;
}, <FormatAppliers>{});
}
13 changes: 12 additions & 1 deletion packages/roosterjs-content-model/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export { ContentModelBr } from './publicTypes/segment/ContentModelBr';
export { ContentModelGeneralSegment } from './publicTypes/segment/ContentModelGeneralSegment';
export { ContentModelSegment } from './publicTypes/segment/ContentModelSegment';

export { FormatHandlerTypeMap, FormatKey } from './publicTypes/format/FormatHandlerTypeMap';
export { ContentModelTableFormat } from './publicTypes/format/ContentModelTableFormat';
export { ContentModelTableCellFormat } from './publicTypes/format/ContentModelTableCellFormat';
export { ContentModelSegmentFormat } from './publicTypes/format/ContentModelSegmentFormat';
Expand Down Expand Up @@ -62,7 +63,12 @@ export {
DomToModelImageSelection,
DomToModelSelectionContext,
} from './publicTypes/context/DomToModelSelectionContext';
export { DomToModelSettings, DefaultStyleMap } from './publicTypes/context/DomToModelSettings';
export {
DomToModelSettings,
DefaultStyleMap,
FormatParser,
FormatParsers,
} from './publicTypes/context/DomToModelSettings';
export { DomToModelContext } from './publicTypes/context/DomToModelContext';
export { ModelToDomContext } from './publicTypes/context/ModelToDomContext';
export {
Expand All @@ -71,6 +77,11 @@ export {
ModelToDomTableSelection,
ModelToDomSelectionContext,
} from './publicTypes/context/ModelToDomSelectionContext';
export {
ModelToDomSettings,
FormatApplier,
FormatAppliers,
} from './publicTypes/context/ModelToDomSettings';
export { ElementProcessor } from './publicTypes/context/ElementProcessor';

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EditorContext } from '../../publicTypes/context/EditorContext';
import { getFormatAppliers } from '../../formatHandlers/defaultFormatHandlers';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';
import { ModelToDomOption } from '../../publicTypes/IExperimentalContentModelEditor';

Expand All @@ -24,5 +25,6 @@ export function createModelToDomContext(
segment: null,
},
},
formatAppliers: getFormatAppliers(options?.formatApplierOverride),
};
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ContentModelFormatBase } from '../../publicTypes/format/ContentModelFormatBase';
import { FormatHandler } from '../../formatHandlers/FormatHandler';
import { FormatKey } from '../../publicTypes/format/FormatHandlerTypeMap';
import { ModelToDomContext } from '../../publicTypes/context/ModelToDomContext';

/**
* @internal
*/
export function applyFormat<T extends ContentModelFormatBase>(
element: HTMLElement,
handlers: FormatHandler<T>[],
handlerKeys: FormatKey[],
format: T,
context: ModelToDomContext
) {
handlers.forEach(handler => {
handler.apply(format, element, context);
handlerKeys.forEach(key => {
context.formatAppliers[key](format, element, context);
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ContentModelDocument } from './block/group/ContentModelDocument';
import { DefaultStyleMap } from './context/DomToModelSettings';
import { DefaultStyleMap, FormatParsers } from './context/DomToModelSettings';
import { EditorContext } from './context/EditorContext';
import { ElementProcessor } from './context/ElementProcessor';
import { FormatAppliers } from './context/ModelToDomSettings';
import { IEditor } from 'roosterjs-editor-types';

/**
Expand All @@ -17,14 +18,18 @@ export interface DomToModelOption {
* Overrides default element styles
*/
defaultStyleOverride?: DefaultStyleMap;

/**
* Overrides default format handlers
*/
formatParserOverride?: Partial<FormatParsers>;
}

/**
* Options for creating ModelToDomContext
*/
// tslint:disable no-empty-interface
export interface ModelToDomOption {
// TODO: Add options here
formatApplierOverride?: Partial<FormatAppliers>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import { ContentModelFormatBase } from '../format/ContentModelFormatBase';
import { DomToModelContext } from './DomToModelContext';
import { ElementProcessor } from './ElementProcessor';
import { FormatHandlerTypeMap, FormatKey } from '../format/FormatHandlerTypeMap';

/**
* A type of Default style map, from tag name string (in upper case) to a static style object
*/
export type DefaultStyleMap = Record<string, Partial<CSSStyleDeclaration>>;

/**
* Parse format from the given HTML element and default style
* @param format The format object to parse into
* @param element The HTML element to parse format from
* @param context The context object that provide related context information
* @param defaultStyle Default CSS style of the given HTML element
*/
export type FormatParser<TFormat extends ContentModelFormatBase> = (
format: TFormat,
element: HTMLElement,
context: DomToModelContext,
defaultStyle: Readonly<Partial<CSSStyleDeclaration>>
) => void;

/**
* All format parsers
*/
export type FormatParsers = {
[Key in FormatKey]: FormatParser<FormatHandlerTypeMap[Key]>;
};

/**
* Represents settings to customize DOM to Content Model conversion
*/
Expand All @@ -18,4 +42,9 @@ export interface DomToModelSettings {
* Map of default styles
*/
defaultStyles: DefaultStyleMap;

/**
* Map of format parsers
*/
formatParsers: FormatParsers;
}
Loading