diff --git a/.eslintrc.js b/.eslintrc.js index 62b68460026..ec55b455982 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -150,5 +150,20 @@ module.exports = { 'prefer-const': 'error', 'no-var': 'error', 'etc/no-const-enum': ['error', { allowLocal: true }], + 'import/no-default-export': 'error', }, + overrides: [ + { + files: [ + 'roosterjs-editor-*/**/*.ts', + 'roosterjs-react/**/*.ts', + 'roosterjs-react/**/*.tsx', + 'roosterjs-color-utils/**/*.ts', + 'roosterjs/**/*.ts', + ], + rules: { + 'import/no-default-export': 'off', + }, + }, + ], }; diff --git a/README.md b/README.md index 2f2087c0bb5..611d90f8c36 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,17 @@ Rooster is a framework-independent JavaScript rich-text editor neatly nested inside one HTML `
` element. Editing operations performed by end users are handled in simple ways to generate the final HTML. -To view the sample site, please click the link below: +Rooster is working on top of a middle layer data structure called "Content Model". +All format API and editing operation are using this Content Model layer as content format, +and finally convert to HTML and show it in editor. -[RoosterJs Sample Site](https://microsoft.github.io/roosterjs/index.html). +To view the demo site, please click the link below: -## Upgrade from RoosterJs 7.\* +[RoosterJs Demo Site](https://microsoft.github.io/roosterjs/index.html). -Please see [here](https://github.com/microsoft/roosterjs/wiki/RoosterJs-8). +## Upgrade from RoosterJs 8.\* + +Please see [here](https://github.com/microsoft/roosterjs/wiki/RoosterJs-9). ## Features @@ -25,24 +29,22 @@ Rooster contains 6 basic packages. `createEditor()` function in roosterjs to create an editor with default configurations. -2. [roosterjs-editor-core](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_core.html): - Defines the core editor and plugin infrastructure. Use `roosterjs-editor-core` +2. [roosterjs-content-model-core](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_content_model_core.html): + Defines the core editor and plugin infrastructure. Use `roosterjs-content-model-core` instead of `roosterjs` to build and customize your own editor. -3. [roosterjs-editor-api](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_api.html): +3. [roosterjs-content-model-api](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_content_model_api.html): Defines APIs for editor operations. Use these APIs to modify content and - formatting in the editor you built using `roosterjs-editor-core`. + formatting in the editor you built using `roosterjs-content-model-core`. -4. [roosterjs-editor-dom](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_dom.html): - Defines APIs for DOM operations. Use `roosterjs-editor-api` instead unless - you want to access DOM API directly. +4. [roosterjs-content-model-dom](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_content_model_dom.html): + Defines APIs for Content Model and DOM operations. This package do conversion between DOM tree and roosterjs Content Model. -5. [roosterjs-editor-plugins](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_plugins.html): - Defines basic plugins for common features. Examples: making hyperlinks, - pasting HTML content, inserting inline images. +5. [roosterjs-content-model-plugins](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_content_model_plugins.html): + Defines basic plugins for common features. -6. [roosterjs-editor-types](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_types.html): - Defines public interfaces and enumerations. +6. [roosterjs-content-model-types](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_content_model_types.html): + Defines public interfaces and enumerations, including Content Model types, API parameters and other types. There are also some extension packages to provide additional functionalities. @@ -52,30 +54,38 @@ There are also some extension packages to provide additional functionalities. 2. [roosterjs-react](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_react.html): Provide a React wrapper of roosterjs so it can be easily used with React. -3. [roosterjs-editor-types-compatible](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_types_compatible.html): - Provide types that are compatible with isolatedModules mode. When using isolatedModules mode, - "const enum" will not work correctly, this package provides enums with prefix "Compatible" in - their names and they have the same value with const enums in roosterjs-editor-types package +To be compatible with old (8.\*) versions, you can use `EditorAdapter` class from the following package which can act as a 8.\* Editor: + +1. [roosterjs-editor-adapter](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_adapter.html): + Provide a adapter class `EditorAdapter` to work with Editor (9.\*) and legacy plugins (via [EditorAdapterOptions.legacyPlugins](https://microsoft.github.io/roosterjs/docs/interfaces/roosterjs_editor_adapter.editoradapteroptions.html#legacyplugins)) + +And the following packages are for old (8.\*) compatibility: + +1. [roosterjs-editor-core](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_core.html): +2. [roosterjs-editor-api](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_api.html): +3. [roosterjs-editor-dom](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_dom.html): +4. [roosterjs-editor-plugins](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_plugins.html): +5. [roosterjs-editor-types](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_types.html): +6. [roosterjs-editor-types-compatible](https://microsoft.github.io/roosterjs/docs/modules/roosterjs_editor_types_compatible.html): ### APIs -Rooster provides DOM level APIs (in `roosterjs-editor-dom`), core APIs (in `roosterjs-editor-core`), and formatting APIs -(in `roosterjs-editor-api`) to perform editing operations. +Rooster provides Content Model level APIs (in `roosterjs-content-model-dom`), core APIs (in `roosterjs-content-model-core`), and formatting APIs +(in `roosterjs-content-modelapi`) to perform editing operations. -`roosterjs-editor-dom` provides several levels of DOM operations: +`roosterjs-content-model-dom` provides several levels of Content Model operations: -- Perform basic DOM operations such as `wrap()`, `unwrap()`, ... -- Wrap a given DOM node with `InlineElement` or `BlockElement` and perform - operations with DOM Walker API. -- Perform DOM operations on a given scope using scopers. -- Travel among `InlineElements` and `BlockElements` with scope using - ContentTraverser API. +- Create Content Model elements +- Convert DOM tree to Content Model +- Convert Content Model to DOM tree +- Format handlers +- A few DOM level API -`roosterjs-editor-core` provides APIs for editor core. Editor class will call such -APIs to perform basic editor operations. These APIs are overridable by specifying +`roosterjs-content-model-core` provides APIs for editor core. Editor class will call such +APIs to perform basic editor operations. These APIs can be overridden by specifying API overrides in Editor options when creating the editor. -`roosterjs-editor-api` provides APIs for scenario-based operations triggered by +`roosterjs-content-model-api` provides APIs for scenario-based operations triggered by user interaction. ## Plugins @@ -99,7 +109,7 @@ class HelloRooster implements EditorPlugin { dispose() {} onPluginEvent(e: PluginEvent) { - if (e.eventType == PluginEventType.KeyPress && e.rawEvent.which == 65) { + if (e.eventType == 'input' && e.rawEvent.which == 65) { alert('Hello Rooster'); } } @@ -114,9 +124,9 @@ Install via NPM or Yarn: You can also install sub packages separately: -`yarn add roosterjs-editor-core` +`yarn add roosterjs-content-model-core` -`yarn add roosterjs-editor-api` +`yarn add roosterjs-content-model-api` `...` @@ -161,9 +171,9 @@ In order to run the code below, you may also need to install [webpack](https://w ## Sample code -To view the sample site, please click [here](https://microsoft.github.io/roosterjs/index.html). +To view the demo site, please click [here](https://microsoft.github.io/roosterjs/index.html). -To build the sample site code yourself, follow these instructions: +To build the demo site code yourself, follow these instructions: 1. Get dependencies using [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com/): diff --git a/demo/index.html b/demo/index.html index b63af748584..8dbbf68981c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -21,8 +21,8 @@ + - diff --git a/demo/scripts/controls/sidePane/editorOptions/OptionsPane.tsx b/demo/scripts/controls/sidePane/editorOptions/OptionsPane.tsx index 88183cdbf93..3716c3b8831 100644 --- a/demo/scripts/controls/sidePane/editorOptions/OptionsPane.tsx +++ b/demo/scripts/controls/sidePane/editorOptions/OptionsPane.tsx @@ -23,7 +23,7 @@ const htmlButtons = '\n'; const darkButton = '\n'; const htmlEnd = - '\n' + + '\n' + '\n' + ''; @@ -34,7 +34,7 @@ const htmlRoosterReact = '\n' + '\n' + '\n' + - '\n' + + '\n' + '\n' + '\n' + ''; diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/ButtonsCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/ButtonsCode.ts index 7e683cfbaa9..a1cf2eb63d4 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/ButtonsCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/ButtonsCode.ts @@ -2,11 +2,11 @@ import CodeElement from './CodeElement'; import { getObjectKeys } from 'roosterjs-editor-dom'; const codeMap: { [id: string]: string } = { - buttonB: 'roosterjs.toggleBold(editor)', - buttonI: 'roosterjs.toggleItalic(editor)', - buttonU: 'roosterjs.toggleUnderline(editor)', - buttonBullet: 'roosterjs.toggleBullet(editor)', - buttonNumbering: 'roosterjs.toggleNumbering(editor)', + buttonB: 'roosterjsLegacy.toggleBold(editor)', + buttonI: 'roosterjsLegacy.toggleItalic(editor)', + buttonU: 'roosterjsLegacy.toggleUnderline(editor)', + buttonBullet: 'roosterjsLegacy.toggleBullet(editor)', + buttonNumbering: 'roosterjsLegacy.toggleNumbering(editor)', buttonUndo: 'editor.undo()', buttonRedo: 'editor.redo()', }; diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/ContentEditCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/ContentEditCode.ts index 5b2ed0c6ae4..8385ecf27fb 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/ContentEditCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/ContentEditCode.ts @@ -10,6 +10,6 @@ export default class ContentEditCode extends CodeElement { } getCode() { - return 'new roosterjs.ContentEdit(' + this.features.getCode() + ')'; + return 'new roosterjsLegacy.ContentEdit(' + this.features.getCode() + ')'; } } diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/DarkModeCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/DarkModeCode.ts index 58b6fe8ca38..37632f99724 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/DarkModeCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/DarkModeCode.ts @@ -2,6 +2,6 @@ import CodeElement from './CodeElement'; export default class DarkModeCode extends CodeElement { getCode() { - return 'roosterjs.getDarkColor'; + return 'roosterjsLegacy.getDarkColor'; } } diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/EditorCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/EditorCode.ts index 1ed34a9b7cd..269a04a0a58 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/EditorCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/EditorCode.ts @@ -27,10 +27,10 @@ export default class EditorCode extends CodeElement { let defaultFormat = this.defaultFormat.getCode(); let expermientalFeatures = this.experimentalFeatures.getCode(); let darkMode = this.darkMode.getCode(); - let code = "let contentDiv = document.getElementById('contentDiv') as HTMLDivElement;\n"; + let code = "let contentDiv = document.getElementById('contentDiv');\n"; code += `let plugins = ${this.plugins.getCode()};\n`; code += defaultFormat ? `let defaultFormat: DefaultFormat = ${defaultFormat};\n` : ''; - code += 'let options: roosterjs.EditorOptions = {\n'; + code += 'let options = {\n'; code += this.indent('plugins: plugins,\n'); code += defaultFormat ? this.indent('defaultFormat: defaultFormat,\n') : ''; code += expermientalFeatures @@ -38,7 +38,7 @@ export default class EditorCode extends CodeElement { : ''; code += darkMode ? this.indent(`getDarkColor: ${darkMode},\n`) : ''; code += '};\n'; - code += 'let editor = new roosterjs.Editor(contentDiv, options);\n'; + code += 'let editor = new roosterjsLegacy.Editor(contentDiv, options);\n'; code += this.buttons ? this.buttons.getCode() : ''; return code; diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/HyperLinkCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/HyperLinkCode.ts index d8dbc3d4d4d..f746ddec065 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/HyperLinkCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/HyperLinkCode.ts @@ -7,7 +7,7 @@ export default class HyperLinkCode extends CodeElement { } getCode() { - return 'new roosterjs.HyperLink(' + this.getLinkCallback() + ')'; + return 'new roosterjsLegacy.HyperLink(' + this.getLinkCallback() + ')'; } private getLinkCallback() { diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts index 10fa248b3b0..1e102ee7270 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/PluginsCode.ts @@ -4,12 +4,7 @@ import ContentEditCode from './ContentEditCode'; import HyperLinkCode from './HyperLinkCode'; import TableCellSelectionCode from './TableCellSelectionCode'; import WatermarkCode from './WatermarkCode'; -import { - CustomReplaceCode, - CutPasteListChainCode, - ImageEditCode, - ContentModelPasteCode, -} from './SimplePluginCode'; +import { CustomReplaceCode, CutPasteListChainCode, ImageEditCode } from './SimplePluginCode'; export default class PluginsCode extends CodeElement { private plugins: CodeElement[]; @@ -21,7 +16,6 @@ export default class PluginsCode extends CodeElement { this.plugins = [ pluginList.contentEdit && new ContentEditCode(state.contentEditFeatures), pluginList.hyperlink && new HyperLinkCode(state.linkTitle), - new ContentModelPasteCode(), pluginList.watermark && new WatermarkCode(this.state.watermarkText), pluginList.imageEdit && new ImageEditCode(), pluginList.cutPasteListChain && new CutPasteListChainCode(), diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/ReactEditorCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/ReactEditorCode.ts index 3e8e88649e4..7ba7de5a1fd 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/ReactEditorCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/ReactEditorCode.ts @@ -41,8 +41,8 @@ export default class ReactEditorCode extends CodeElement { } code += `let plugins = ${this.plugins.getCode()};\n`; - code += defaultFormat ? `let defaultFormat: DefaultFormat = ${defaultFormat};\n` : ''; - code += 'let options: roosterjs.EditorOptions = {\n'; + code += defaultFormat ? `let defaultFormat = ${defaultFormat};\n` : ''; + code += 'let options = {\n'; code += this.indent('plugins: plugins,\n'); code += defaultFormat ? this.indent('defaultFormat: defaultFormat,\n') : ''; code += expermientalFeatures diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/SimplePluginCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/SimplePluginCode.ts index 442679efc19..221d595dc1b 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/SimplePluginCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/SimplePluginCode.ts @@ -1,7 +1,7 @@ import CodeElement from './CodeElement'; class SimplePluginCode extends CodeElement { - constructor(private name: string, private namespace: string = 'roosterjs') { + constructor(private name: string, private namespace: string = 'roosterjsLegacy') { super(); } @@ -16,12 +16,6 @@ export class PasteCode extends SimplePluginCode { } } -export class ContentModelPasteCode extends SimplePluginCode { - constructor() { - super('PastePlugin', 'roosterjsContentModel'); - } -} - export class ImageEditCode extends SimplePluginCode { constructor() { super('ImageEdit'); diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/TableCellSelectionCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/TableCellSelectionCode.ts index 248e3f870f6..1e6315055c0 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/TableCellSelectionCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/TableCellSelectionCode.ts @@ -6,6 +6,6 @@ export default class TableCellSelectionCode extends CodeElement { } getCode() { - return 'new roosterjs.TableCellSelection()'; + return 'new roosterjsLegacy.TableCellSelection()'; } } diff --git a/demo/scripts/controls/sidePane/editorOptions/codes/WatermarkCode.ts b/demo/scripts/controls/sidePane/editorOptions/codes/WatermarkCode.ts index 41855c190cd..4e8ed6b1078 100644 --- a/demo/scripts/controls/sidePane/editorOptions/codes/WatermarkCode.ts +++ b/demo/scripts/controls/sidePane/editorOptions/codes/WatermarkCode.ts @@ -6,6 +6,6 @@ export default class WatermarkCode extends CodeElement { } getCode() { - return `new roosterjs.Watermark('${this.encode(this.watermarkText)}')`; + return `new roosterjsLegacy.Watermark('${this.encode(this.watermarkText)}')`; } } diff --git a/demo/scripts/controlsV2/demoButtons/darkModeButton.ts b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts index 32f773e8039..00d03c1a5d7 100644 --- a/demo/scripts/controlsV2/demoButtons/darkModeButton.ts +++ b/demo/scripts/controlsV2/demoButtons/darkModeButton.ts @@ -21,4 +21,9 @@ export const darkModeButton: RibbonButton = { MainPane.getInstance().toggleDarkMode(); return true; }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/demoButtons/exportContentButton.ts b/demo/scripts/controlsV2/demoButtons/exportContentButton.ts index 44807386d04..8cf6371219a 100644 --- a/demo/scripts/controlsV2/demoButtons/exportContentButton.ts +++ b/demo/scripts/controlsV2/demoButtons/exportContentButton.ts @@ -19,4 +19,9 @@ export const exportContentButton: RibbonButton = { const html = exportContent(editor); win.document.write(editor.getTrustedHTMLHandler()(html)); }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts b/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts index a5cc4a75611..ee406679c60 100644 --- a/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts +++ b/demo/scripts/controlsV2/demoButtons/formatPainterButton.ts @@ -1,15 +1,19 @@ -import { FormatPainterPlugin } from '../plugins/FormatPainterPlugin'; +import { FormatPainterHandler } from '../plugins/FormatPainterPlugin'; import type { RibbonButton } from '../roosterjsReact/ribbon'; /** * @internal * "Format Painter" button on the format ribbon */ -export const formatPainterButton: RibbonButton<'formatPainter'> = { - key: 'formatPainter', - unlocalizedText: 'Format painter', - iconName: 'Brush', - onClick: () => { - FormatPainterPlugin.startFormatPainter(); - }, -}; +export function createFormatPainterButton( + handler: FormatPainterHandler +): RibbonButton<'formatPainter'> { + return { + key: 'formatPainter', + unlocalizedText: 'Format painter', + iconName: 'Brush', + onClick: () => { + handler.startFormatPainter(); + }, + }; +} diff --git a/demo/scripts/controlsV2/demoButtons/formatTableButton.ts b/demo/scripts/controlsV2/demoButtons/formatTableButton.ts index 96edc49a96c..6a5ba0535f3 100644 --- a/demo/scripts/controlsV2/demoButtons/formatTableButton.ts +++ b/demo/scripts/controlsV2/demoButtons/formatTableButton.ts @@ -1,5 +1,5 @@ import { formatTable } from 'roosterjs-content-model-api'; -import { TableBorderFormat } from 'roosterjs-content-model-core'; +import { TableBorderFormat } from 'roosterjs-content-model-dom'; import { TableMetadataFormat } from 'roosterjs-content-model-types'; import type { RibbonButton } from '../roosterjsReact/ribbon'; diff --git a/demo/scripts/controlsV2/demoButtons/pasteButton.ts b/demo/scripts/controlsV2/demoButtons/pasteButton.ts index 4d97de90a8e..d880ba95596 100644 --- a/demo/scripts/controlsV2/demoButtons/pasteButton.ts +++ b/demo/scripts/controlsV2/demoButtons/pasteButton.ts @@ -1,4 +1,5 @@ -import { extractClipboardItems, paste } from 'roosterjs-content-model-core'; +import { extractClipboardItems } from 'roosterjs-content-model-dom'; +import { paste } from 'roosterjs-content-model-core'; import type { RibbonButton } from '../roosterjsReact/ribbon'; /** diff --git a/demo/scripts/controlsV2/demoButtons/popoutButton.ts b/demo/scripts/controlsV2/demoButtons/popoutButton.ts index 5814cdafd60..98b214ed413 100644 --- a/demo/scripts/controlsV2/demoButtons/popoutButton.ts +++ b/demo/scripts/controlsV2/demoButtons/popoutButton.ts @@ -17,4 +17,9 @@ export const popoutButton: RibbonButton = { onClick: _ => { MainPane.getInstance().popout(); }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts b/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts index c3f55c02674..708a9e8d23a 100644 --- a/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/setBulletedListStyleButton.ts @@ -1,4 +1,4 @@ -import { BulletListType } from 'roosterjs-content-model-core'; +import { BulletListType } from 'roosterjs-content-model-dom'; import { setListStyle } from 'roosterjs-content-model-api'; import type { RibbonButton } from '../roosterjsReact/ribbon'; diff --git a/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts b/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts index ccc31cc604b..6796b38e870 100644 --- a/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts +++ b/demo/scripts/controlsV2/demoButtons/setNumberedListStyleButton.ts @@ -1,4 +1,4 @@ -import { NumberingListType } from 'roosterjs-content-model-core'; +import { NumberingListType } from 'roosterjs-content-model-dom'; import { setListStyle } from 'roosterjs-content-model-api'; import type { RibbonButton } from '../roosterjsReact/ribbon'; diff --git a/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts b/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts index fe0bc42df27..fc7c937416e 100644 --- a/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts +++ b/demo/scripts/controlsV2/demoButtons/tableEditButtons.ts @@ -84,7 +84,7 @@ export const tableMergeButton: RibbonButton< 'ribbonButtonTableMerge' | TableEditMergeMenuItemStringKey > = { key: 'ribbonButtonTableMerge', - iconName: 'TableComputed', + iconName: '', unlocalizedText: 'Merge', isDisabled: formatState => !formatState.isInTable, dropDownMenu: { @@ -102,13 +102,16 @@ export const tableMergeButton: RibbonButton< editTable(editor, TableEditOperationMap[key]); } }, + commandBarProperties: { + iconOnly: false, + }, }; export const tableSplitButton: RibbonButton< 'ribbonButtonTableSplit' | TableEditSplitMenuItemStringKey > = { key: 'ribbonButtonTableSplit', - iconName: 'TableComputed', + iconName: '', unlocalizedText: 'Split', isDisabled: formatState => !formatState.isInTable, dropDownMenu: { @@ -122,13 +125,16 @@ export const tableSplitButton: RibbonButton< editTable(editor, TableEditOperationMap[key]); } }, + commandBarProperties: { + iconOnly: false, + }, }; export const tableAlignCellButton: RibbonButton< 'ribbonButtonTableAlignCell' | TableEditAlignMenuItemStringKey > = { key: 'ribbonButtonTableAlignCell', - iconName: 'TableComputed', + iconName: '', unlocalizedText: 'Align table cell', isDisabled: formatState => !formatState.isInTable, dropDownMenu: { @@ -147,13 +153,16 @@ export const tableAlignCellButton: RibbonButton< editTable(editor, TableEditOperationMap[key]); } }, + commandBarProperties: { + iconOnly: false, + }, }; export const tableAlignTableButton: RibbonButton< 'ribbonButtonTableAlignTable' | TableEditAlignTableMenuItemStringKey > = { key: 'ribbonButtonTableAlignTable', - iconName: 'TableComputed', + iconName: '', unlocalizedText: 'Align table', isDisabled: formatState => !formatState.isInTable, dropDownMenu: { @@ -168,4 +177,7 @@ export const tableAlignTableButton: RibbonButton< editTable(editor, TableEditOperationMap[key]); } }, + commandBarProperties: { + iconOnly: false, + }, }; diff --git a/demo/scripts/controlsV2/demoButtons/zoomButton.ts b/demo/scripts/controlsV2/demoButtons/zoomButton.ts index ecfa48d4c96..ceaf11bb688 100644 --- a/demo/scripts/controlsV2/demoButtons/zoomButton.ts +++ b/demo/scripts/controlsV2/demoButtons/zoomButton.ts @@ -41,4 +41,10 @@ export const zoomButton: RibbonButton = { editor.triggerEvent('zoomChanged', { newZoomScale: zoomScale }); }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + menuIcon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/mainPane/MainPane.scss b/demo/scripts/controlsV2/mainPane/MainPane.scss index 1739df86547..88b2240b212 100644 --- a/demo/scripts/controlsV2/mainPane/MainPane.scss +++ b/demo/scripts/controlsV2/mainPane/MainPane.scss @@ -12,6 +12,13 @@ overflow-x: hidden; } +.menuTab { + border-radius: 30% 30% 0 0; + background-color: $primaryLighter; + color: white; + font-weight: bold; +} + .body { flex: 1 1 auto; position: relative; diff --git a/demo/scripts/controlsV2/mainPane/MainPane.tsx b/demo/scripts/controlsV2/mainPane/MainPane.tsx index aab2f694eef..8ef4939beee 100644 --- a/demo/scripts/controlsV2/mainPane/MainPane.tsx +++ b/demo/scripts/controlsV2/mainPane/MainPane.tsx @@ -3,7 +3,6 @@ import * as ReactDOM from 'react-dom'; import SampleEntityPlugin from '../plugins/SampleEntityPlugin'; import { ApiPlaygroundPlugin } from '../sidePane/apiPlayground/ApiPlaygroundPlugin'; import { Border, ContentModelDocument, EditorOptions } from 'roosterjs-content-model-types'; -import { buttons, buttonsWithPopout } from './ribbonButtons'; import { Colors, EditorPlugin, IEditor, Snapshots } from 'roosterjs-content-model-types'; import { ContentModelPanePlugin } from '../sidePane/contentModel/ContentModelPanePlugin'; import { createEmojiPlugin } from '../roosterjsReact/emoji'; @@ -11,16 +10,24 @@ import { createImageEditMenuProvider } from '../roosterjsReact/contextMenu/menus import { createLegacyPlugins } from '../plugins/createLegacyPlugins'; import { createListEditMenuProvider } from '../roosterjsReact/contextMenu/menus/createListEditMenuProvider'; import { createPasteOptionPlugin } from '../roosterjsReact/pasteOptions'; -import { createRibbonPlugin, Ribbon, RibbonPlugin } from '../roosterjsReact/ribbon'; +import { createRibbonPlugin, Ribbon, RibbonButton, RibbonPlugin } from '../roosterjsReact/ribbon'; +import { darkModeButton } from '../demoButtons/darkModeButton'; import { Editor } from 'roosterjs-content-model-core'; import { EditorAdapter } from 'roosterjs-editor-adapter'; import { EditorOptionsPlugin } from '../sidePane/editorOptions/EditorOptionsPlugin'; import { EventViewPlugin } from '../sidePane/eventViewer/EventViewPlugin'; +import { exportContentButton } from '../demoButtons/exportContentButton'; import { FormatPainterPlugin } from '../plugins/FormatPainterPlugin'; import { FormatStatePlugin } from '../sidePane/formatState/FormatStatePlugin'; +import { getButtons } from '../tabs/ribbonButtons'; import { getDarkColor } from 'roosterjs-color-utils'; +import { getPresetModelById } from '../sidePane/presets/allPresets/allPresets'; +import { getTabs, tabNames } from '../tabs/getTabs'; import { getTheme } from '../theme/themes'; import { OptionState } from '../sidePane/editorOptions/OptionState'; +import { popoutButton } from '../demoButtons/popoutButton'; +import { PresetPlugin } from '../sidePane/presets/PresetPlugin'; +import { redoButton } from '../roosterjsReact/ribbon/buttons/redoButton'; import { registerWindowForCss, unregisterWindowForCss } from '../../utils/cssMonitor'; import { Rooster } from '../roosterjsReact/rooster'; import { SidePane } from '../sidePane/SidePane'; @@ -29,8 +36,10 @@ import { SnapshotPlugin } from '../sidePane/snapshot/SnapshotPlugin'; import { ThemeProvider } from '@fluentui/react/lib/Theme'; import { TitleBar } from '../titleBar/TitleBar'; import { trustedHTMLHandler } from '../../utils/trustedHTMLHandler'; +import { undoButton } from '../roosterjsReact/ribbon/buttons/undoButton'; import { UpdateContentPlugin } from '../plugins/UpdateContentPlugin'; import { WindowProvider } from '@fluentui/react/lib/WindowProvider'; +import { zoomButton } from '../demoButtons/zoomButton'; import { createContextMenuPlugin, createTableEditMenuProvider, @@ -38,9 +47,11 @@ import { import { AutoFormatPlugin, EditPlugin, + MarkdownPlugin, PastePlugin, ShortcutPlugin, TableEditPlugin, + WatermarkPlugin, } from 'roosterjs-content-model-plugins'; const styles = require('./MainPane.scss'); @@ -52,6 +63,7 @@ export interface MainPaneState { scale: number; isDarkMode: boolean; isRtl: boolean; + activeTab: tabNames; tableBorderFormat?: Border; editorCreator: (div: HTMLDivElement, options: EditorOptions) => IEditor; } @@ -71,6 +83,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { private eventViewPlugin: EventViewPlugin; private apiPlaygroundPlugin: ApiPlaygroundPlugin; private contentModelPanePlugin: ContentModelPanePlugin; + private presetPlugin: PresetPlugin; private ribbonPlugin: RibbonPlugin; private snapshotPlugin: SnapshotPlugin; private formatPainterPlugin: FormatPainterPlugin; @@ -108,8 +121,10 @@ export class MainPane extends React.Component<{}, MainPaneState> { this.apiPlaygroundPlugin = new ApiPlaygroundPlugin(); this.snapshotPlugin = new SnapshotPlugin(this.snapshots); this.contentModelPanePlugin = new ContentModelPanePlugin(); + this.presetPlugin = new PresetPlugin(); this.ribbonPlugin = createRibbonPlugin(); this.formatPainterPlugin = new FormatPainterPlugin(); + this.state = { showSidePane: window.location.hash != '', popoutWindow: null, @@ -123,17 +138,17 @@ export class MainPane extends React.Component<{}, MainPaneState> { style: 'solid', color: '#ABABAB', }, + activeTab: 'all', }; } render() { + const theme = getTheme(this.state.isDarkMode); return ( - + {this.renderTitleBar()} - {!this.state.popoutWindow && this.renderRibbon(false /*isPopout*/)} + {!this.state.popoutWindow && this.renderTabs()} + {!this.state.popoutWindow && this.renderRibbon()}
{this.state.popoutWindow ? this.renderPopout() : this.renderMainPane()}
@@ -212,6 +227,16 @@ export class MainPane extends React.Component<{}, MainPaneState> { }); } + changeRibbon(id: tabNames): void { + this.setState({ + activeTab: id, + }); + } + + setPreset(preset: ContentModelDocument) { + this.model = preset; + } + setPageDirection(isRtl: boolean): void { this.setState({ isRtl: isRtl }); [window, this.state.popoutWindow].forEach(win => { @@ -225,10 +250,32 @@ export class MainPane extends React.Component<{}, MainPaneState> { return ; } - private renderRibbon(isPopout: boolean) { + private renderTabs() { + const tabs = getTabs(); + const topRightButtons: RibbonButton[] = [ + undoButton, + redoButton, + zoomButton, + darkModeButton, + exportContentButton, + ]; + this.state.popoutWindow ? null : topRightButtons.push(popoutButton); + + return ( +
+ + +
+ ); + } + private renderRibbon() { return ( @@ -263,6 +310,13 @@ export class MainPane extends React.Component<{}, MainPaneState> { } private renderEditor() { + // Set preset if found + const search = new URLSearchParams(document.location.search); + const hasPreset = search.get('preset'); + if (hasPreset) { + this.setPreset(getPresetModelById(hasPreset)); + } + const editorStyles = { transform: `scale(${this.state.scale})`, transformOrigin: this.state.isRtl ? 'right top' : 'left top', @@ -300,7 +354,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { editorCreator={this.state.editorCreator} dir={this.state.isRtl ? 'rtl' : 'ltr'} knownColors={this.knownColors} - cacheModel={this.state.initState.cacheModel} + disableCache={this.state.initState.disableCache} /> )}
@@ -345,7 +399,8 @@ export class MainPane extends React.Component<{}, MainPaneState> {
- {this.renderRibbon(true /*isPopout*/)} + {this.renderTabs()} + {this.renderRibbon()}
{this.renderEditor()}
@@ -407,6 +462,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { this.apiPlaygroundPlugin, this.snapshotPlugin, this.contentModelPanePlugin, + this.presetPlugin, ]; } @@ -417,13 +473,27 @@ export class MainPane extends React.Component<{}, MainPaneState> { listMenu, tableMenu, imageMenu, + 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(), pluginList.tableEdit && new TableEditPlugin(), + pluginList.watermark && new WatermarkPlugin(watermarkText), + pluginList.markdown && + new MarkdownPlugin({ + bold: true, + italic: true, + strikethrough: true, + }), pluginList.emoji && createEmojiPlugin(), pluginList.pasteOption && createPasteOptionPlugin(), pluginList.sampleEntity && new SampleEntityPlugin(), diff --git a/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts b/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts index 8bf0968bb10..8c2798aa6a7 100644 --- a/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts +++ b/demo/scripts/controlsV2/plugins/FormatPainterPlugin.ts @@ -1,5 +1,4 @@ import { applySegmentFormat, getFormatState } from 'roosterjs-content-model-api'; -import { MainPane } from '../mainPane/MainPane'; import { ContentModelSegmentFormat, EditorPlugin, @@ -9,16 +8,26 @@ import { const FORMATPAINTERCURSOR_SVG = require('./formatpaintercursor.svg'); const FORMATPAINTERCURSOR_STYLE = `cursor: url("${FORMATPAINTERCURSOR_SVG}") 8.5 16, auto`; +const FORMAT_PAINTER_STYLE_KEY = '_FormatPainter'; + +/** + * Format painter handler works together with a format painter button tot let implement format painter functioinality + */ +export interface FormatPainterHandler { + /** + * Let editor enter format painter state + */ + startFormatPainter(): void; +} -export class FormatPainterPlugin implements EditorPlugin { +/** + * Format painter plugin helps implement format painter functionality. + * To use this plugin, you need a button to let editor enter format painter state by calling formatPainterPlugin.startFormatPainter(), + * then this plugin will handle the rest work. + */ +export class FormatPainterPlugin implements EditorPlugin, FormatPainterHandler { private editor: IEditor | null = null; - private styleNode: HTMLStyleElement | null = null; private painterFormat: ContentModelSegmentFormat | null = null; - private static instance: FormatPainterPlugin | undefined; - - constructor() { - FormatPainterPlugin.instance = this; - } getName() { return 'FormatPainter'; @@ -26,20 +35,10 @@ export class FormatPainterPlugin implements EditorPlugin { initialize(editor: IEditor) { this.editor = editor; - - const doc = this.editor.getDocument(); - this.styleNode = doc.createElement('style'); - - doc.head.appendChild(this.styleNode); } dispose() { this.editor = null; - - if (this.styleNode) { - this.styleNode.parentNode?.removeChild(this.styleNode); - this.styleNode = null; - } } onPluginEvent(event: PluginEvent) { @@ -53,26 +52,19 @@ export class FormatPainterPlugin implements EditorPlugin { } private setFormatPainterCursor(format: ContentModelSegmentFormat | null) { - const sheet = this.styleNode.sheet; - - if (this.painterFormat) { - for (let i = sheet.cssRules.length - 1; i >= 0; i--) { - sheet.deleteRule(i); - } - } - this.painterFormat = format; - if (this.painterFormat) { - sheet.insertRule(`#${MainPane.editorDivId} {${FORMATPAINTERCURSOR_STYLE}}`); - } + this.editor?.setEditorStyle( + FORMAT_PAINTER_STYLE_KEY, + this.painterFormat ? FORMATPAINTERCURSOR_STYLE : null + ); } - static startFormatPainter() { - const format = getSegmentFormat(this.instance.editor); + startFormatPainter() { + if (this.editor) { + const format = getSegmentFormat(this.editor); - if (format) { - this.instance.setFormatPainterCursor(format); + this.setFormatPainterCursor(format); } } } diff --git a/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts b/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts index f6cd26cf4dc..0e6e1c53229 100644 --- a/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts +++ b/demo/scripts/controlsV2/plugins/createLegacyPlugins.ts @@ -5,8 +5,6 @@ import { CustomReplace, HyperLink, ImageEdit, - TableCellSelection, - Watermark, } from 'roosterjs-editor-plugins'; import { LegacyPluginList, @@ -28,14 +26,12 @@ export function createLegacyPlugins(initState: OptionState): LegacyEditorPlugin[ : null ) : null, - watermark: pluginList.watermark ? new Watermark(initState.watermarkText) : null, imageEdit: pluginList.imageEdit ? new ImageEdit({ preserveRatio: initState.forcePreserveRatio, applyChangesOnMouseUp: initState.applyChangesOnMouseUp, }) : null, - tableCellSelection: pluginList.tableCellSelection ? new TableCellSelection() : null, customReplace: pluginList.customReplace ? new CustomReplace() : null, announce: pluginList.announce ? new Announce(getDefaultStringsMap()) : null, }; diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx index 9968b807e42..78e2b55d9fb 100644 --- a/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/menus/createImageEditMenuProvider.tsx @@ -1,7 +1,7 @@ import { createContextMenuProvider } from '../utils/createContextMenuProvider'; import { EditorPlugin, IEditor, ImageEditor } from 'roosterjs-content-model-types'; import { formatImageWithContentModel } from 'roosterjs-content-model-api'; -import { iterateSelections, updateImageMetadata } from 'roosterjs-content-model-core'; +import { iterateSelections, updateImageMetadata } from 'roosterjs-content-model-dom'; import { setImageAltText } from 'roosterjs-content-model-api'; import { showInputDialog } from '../../inputDialog/utils/showInputDialog'; import type { ContextMenuItem } from '../types/ContextMenuItem'; diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts index 1300a4879e4..bbd596f6bda 100644 --- a/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/types/ContextMenuItem.ts @@ -1,4 +1,5 @@ import type { IContextualMenuItem, IContextualMenuProps } from '@fluentui/react/lib/ContextualMenu'; +import type { IIconProps } from '@fluentui/react/lib/Icon'; import type { LocalizedStrings, UIUtilities } from '../../common/index'; import type { IEditor } from 'roosterjs-content-model-types'; @@ -74,6 +75,11 @@ export interface ContextMenuItem { */ itemClassName?: string; + /** + * Icon props for the context menu item + */ + iconProps?: IIconProps; + /** * Use this property to pass in Fluent UI ContextMenu property directly. It will overwrite the values of other conflict properties */ diff --git a/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts b/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts index e8904f25c67..7c350acf928 100644 --- a/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts +++ b/demo/scripts/controlsV2/roosterjsReact/contextMenu/utils/createContextMenuProvider.ts @@ -79,6 +79,7 @@ class ContextMenuProviderImpl text: getLocalizedString(this.strings, item.key, item.unlocalizedText), ariaLabel: getLocalizedString(this.strings, item.key, item.unlocalizedText), onClick: () => this.onClick(item, item.key), + iconProps: item.iconProps, subMenuProps: item.subItems ? { onItemClick: (_, menuItem) => menuItem && this.onClick(item, menuItem.data), diff --git a/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts index 2cfaad965b5..7c6c0a7b004 100644 --- a/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts +++ b/demo/scripts/controlsV2/roosterjsReact/emoji/plugin/createEmojiPlugin.ts @@ -1,9 +1,9 @@ import * as React from 'react'; -import { isModifierKey, iterateSelections, undo } from 'roosterjs-content-model-core'; -import { isNodeOfType } from 'roosterjs-content-model-dom'; +import { isModifierKey, isNodeOfType, iterateSelections } from 'roosterjs-content-model-dom'; import { KeyCodes } from '@fluentui/react/lib/Utilities'; import { MoreEmoji } from '../utils/emojiList'; import { showEmojiCallout } from '../components/showEmojiCallout'; +import { undo } from 'roosterjs-content-model-core'; import type { EmojiICallout } from '../components/showEmojiCallout'; import type { Emoji } from '../type/Emoji'; import type { EmojiPane } from '../components/EmojiPane'; diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx index 2307e35c966..9576cdd066b 100644 --- a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialog.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import InputDialogItem from './InputDialogItem'; import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; -import { Dialog, DialogFooter, DialogType } from '@fluentui/react/lib/Dialog'; import { getLocalizedString } from '../../common/index'; import { getObjectKeys } from 'roosterjs-content-model-dom'; +import { Dialog, DialogFooter, DialogType, IDialogContentProps } from '@fluentui/react/lib/Dialog'; import type { DialogItem } from '../type/DialogItem'; import type { CancelButtonStringKey, @@ -26,6 +26,7 @@ export interface InputDialogProps Record | null; onOk: (values: Record) => void; onCancel: () => void; + rows?: number; } /** @@ -34,11 +35,25 @@ export interface InputDialogProps( props: InputDialogProps ) { - const { items, strings, dialogTitleKey, unlocalizedTitle, onOk, onCancel, onChange } = props; - const dialogContentProps = React.useMemo( + const { + items, + strings, + dialogTitleKey, + unlocalizedTitle, + onOk, + onCancel, + onChange, + rows, + } = props; + const dialogContentProps: IDialogContentProps = React.useMemo( () => ({ type: DialogType.normal, title: getLocalizedString(strings, dialogTitleKey, unlocalizedTitle), + styles: { + innerContent: { + height: rows ? '200px' : undefined, + }, + }, }), [strings, dialogTitleKey, unlocalizedTitle] ); @@ -80,6 +95,7 @@ export default function InputDialog ))} diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx index 4c642972037..69a62c97249 100644 --- a/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/component/InputDialogItem.tsx @@ -15,6 +15,7 @@ export interface InputDialogItemProps; onEnterKey: () => void; onChanged: (itemName: ItemNames, newValue: string) => void; + rows?: number; } const classNames = mergeStyleSets({ @@ -33,7 +34,7 @@ const classNames = mergeStyleSets({ export default function InputDialogItem( props: InputDialogItemProps ) { - const { itemName, strings, items, currentValues, onChanged, onEnterKey } = props; + const { itemName, strings, items, currentValues, onChanged, onEnterKey, rows } = props; const { labelKey, unlocalizedLabel, autoFocus } = items[itemName]; const value = currentValues[itemName]; const onValueChange = React.useCallback( @@ -64,6 +65,8 @@ export default function InputDialogItem diff --git a/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx b/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx index 4a69eb5d869..1e7746ae38e 100644 --- a/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/inputDialog/utils/showInputDialog.tsx @@ -28,7 +28,8 @@ export function showInputDialog - ) => Record | null + ) => Record | null, + rows?: number ): Promise | null> { return new Promise | null>(resolve => { let disposer: null | (() => void) = null; @@ -49,6 +50,7 @@ export function showInputDialog ); diff --git a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts index c5a5fd570ef..949c0cdf73a 100644 --- a/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts +++ b/demo/scripts/controlsV2/roosterjsReact/pasteOptions/plugin/createPasteOptionPlugin.ts @@ -1,7 +1,8 @@ import * as React from 'react'; import { ButtonKeys, Buttons } from '../utils/buttons'; -import { ChangeSource, paste } from 'roosterjs-content-model-core'; +import { ChangeSource } from 'roosterjs-content-model-dom'; import { ClipboardData, IEditor, PluginEvent } from 'roosterjs-content-model-types'; +import { paste } from 'roosterjs-content-model-core'; import { showPasteOptionPane } from '../component/showPasteOptionPane'; import type { PasteOptionPane } from '../component/showPasteOptionPane'; import type { LocalizedStrings, ReactEditorPlugin, UIUtilities } from '../../common/index'; diff --git a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx index e0cd130e693..37418851440 100644 --- a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/insertTableButton.tsx @@ -3,6 +3,7 @@ import { FocusZone, FocusZoneDirection } from '@fluentui/react/lib/FocusZone'; import { insertTable } from 'roosterjs-content-model-api'; import { isNodeOfType } from 'roosterjs-content-model-dom'; import { mergeStyleSets } from '@fluentui/react/lib/Styling'; +import { TableCellCoordinate } from 'roosterjs-content-model-types'; import type { RibbonButton } from '../type/RibbonButton'; import type { InsertTableButtonStringKey } from '../type/RibbonButtonStringKeys'; import type { IContextualMenuItem } from '@fluentui/react/lib/ContextualMenu'; @@ -57,7 +58,7 @@ export const insertTableButton: RibbonButton = { }, }; -function parseKey(key: string): { row: number; col: number } { +function parseKey(key: string): TableCellCoordinate { const [row, col] = key.split(','); return { row: parseInt(row), diff --git a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/redoButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/redoButton.ts index 9719c1bffc1..0f2a68cb78a 100644 --- a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/redoButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/redoButton.ts @@ -14,4 +14,9 @@ export const redoButton: RibbonButton = { onClick: editor => { redo(editor); }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/undoButton.ts b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/undoButton.ts index 8febeb8afd4..8b59d345335 100644 --- a/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/undoButton.ts +++ b/demo/scripts/controlsV2/roosterjsReact/ribbon/buttons/undoButton.ts @@ -14,4 +14,9 @@ export const undoButton: RibbonButton = { onClick: editor => { undo(editor); }, + commandBarProperties: { + buttonStyles: { + icon: { paddingBottom: '10px' }, + }, + }, }; diff --git a/demo/scripts/controlsV2/sidePane/SidePane.tsx b/demo/scripts/controlsV2/sidePane/SidePane.tsx index 887a2b47b08..85f3b42f204 100644 --- a/demo/scripts/controlsV2/sidePane/SidePane.tsx +++ b/demo/scripts/controlsV2/sidePane/SidePane.tsx @@ -22,6 +22,7 @@ export class SidePane extends React.Component { }; window.addEventListener('hashchange', this.updateStateFromHash); + window.location.hash ? this.updateStateFromHash() : this.updateHash(); } componentDidMount() { diff --git a/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx index b7d4e006b90..a492c87dd4b 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/ContentModelPane.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { ContentModelDocumentView } from './components/model/ContentModelDocumentView'; import { exportButton } from './buttons/exportButton'; +import { importModelButton } from './buttons/importModelButton'; import { refreshButton } from './buttons/refreshButton'; import { Ribbon, RibbonButton, RibbonPlugin } from '../../roosterjsReact/ribbon'; import { SidePaneElementProps } from '../SidePaneElement'; @@ -25,7 +26,7 @@ export class ContentModelPane extends React.Component< constructor(props: ContentModelPaneProps) { super(props); - this.contentModelButtons = [refreshButton, exportButton]; + this.contentModelButtons = [refreshButton, exportButton, importModelButton]; this.state = { model: null, diff --git a/demo/scripts/controlsV2/sidePane/contentModel/buttons/importModelButton.ts b/demo/scripts/controlsV2/sidePane/contentModel/buttons/importModelButton.ts new file mode 100644 index 00000000000..92a94a77f98 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/contentModel/buttons/importModelButton.ts @@ -0,0 +1,45 @@ +import { isBlockGroupOfType } from 'roosterjs-content-model-dom'; +import { showInputDialog } from '../../../roosterjsReact/inputDialog/utils/showInputDialog'; +import type { RibbonButton } from '../../../roosterjsReact/ribbon/type/RibbonButton'; + +/** + * @internal + * "Import Model" button on the format ribbon + */ +export const importModelButton: RibbonButton<'buttonNameImportModel'> = { + key: 'buttonNameImportModel', + unlocalizedText: 'Import Model', + iconName: 'Installation', + isChecked: formatState => formatState.isBold, + onClick: (editor, _, strings, uiUtilities) => { + showInputDialog( + uiUtilities, + 'buttonNameImportModel', + 'Import Model', + { + model: { + autoFocus: true, + labelKey: 'buttonNameImportModel' as const, + unlocalizedLabel: 'Insert model', + initValue: '', + }, + }, + strings, + undefined /* onChange */, + 10 /* rows */ + ).then(values => { + try { + const importedModel = JSON.parse(values.model); + if (isBlockGroupOfType(importedModel, 'Document')) { + editor.formatContentModel(model => { + model.blocks = importedModel.blocks; + model.format = importedModel.format; + return true; + }); + } + } catch (e) { + throw new Error('Invalid model'); + } + }); + }, +}; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts index 14091d480e0..315a61f3162 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/BorderFormatRenderers.ts @@ -1,5 +1,5 @@ import { BorderFormat } from 'roosterjs-content-model-types'; -import { combineBorderValue, extractBorderValues } from 'roosterjs-content-model-core'; +import { combineBorderValue, extractBorderValues } from 'roosterjs-content-model-dom'; import { createDropDownFormatRenderer } from '../utils/createDropDownFormatRenderer'; import { createTextFormatRenderer } from '../utils/createTextFormatRenderer'; import { FormatRenderer } from '../utils/FormatRenderer'; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts index 01ebf662005..8566023a7ca 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TableMetadataFormatRenders.ts @@ -2,8 +2,7 @@ import { createCheckboxFormatRenderer } from '../utils/createCheckboxFormatRende import { createColorFormatRenderer } from '../utils/createColorFormatRender'; import { createDropDownFormatRenderer } from '../utils/createDropDownFormatRenderer'; import { FormatRenderer } from '../utils/FormatRenderer'; -import { getObjectKeys } from 'roosterjs-content-model-dom'; -import { TableBorderFormat } from 'roosterjs-content-model-core'; +import { getObjectKeys, TableBorderFormat } from 'roosterjs-content-model-dom'; import { TableMetadataFormat } from 'roosterjs-content-model-types'; export const TableMetadataFormatRenders: FormatRenderer[] = [ diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts index 2da01dcfc71..e6251ee016a 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TextColorFormatRenderer.ts @@ -7,7 +7,13 @@ export const TextColorFormatRenderer: FormatRenderer = createCo TextColorFormat >( 'Text color', - format => (format.textColor ? Color(format.textColor).hex() : ''), + format => { + try { + return format.textColor ? Color(format.textColor).hex() : ''; + } catch (e) { + console.log(e); + } + }, (format, value) => { format.textColor = value; return undefined; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx index 2d61fe4a096..3b3260fde69 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/utils/createTextFormatRenderer.tsx @@ -20,7 +20,7 @@ function TextFormatItem(props: { (newValue: string) => { setValue(newValue); setter?.(format, newValue); - onUpdate(); + onUpdate?.(); }, [setter, format] ); diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx index 801c852e275..fdaf3574a84 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelDocumentView.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { BlockGroupContentView } from './BlockGroupContentView'; import { ContentModelDocument } from 'roosterjs-content-model-types'; import { ContentModelView } from '../ContentModelView'; -import { hasSelectionInBlockGroup } from 'roosterjs-content-model-core'; +import { hasSelectionInBlockGroup } from 'roosterjs-content-model-dom'; const styles = require('./ContentModelDocumentView.scss'); diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx index a335649db28..699969db728 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelFormatContainerView.tsx @@ -5,7 +5,7 @@ import { ContentModelView } from '../ContentModelView'; import { DisplayFormatRenderer } from '../format/formatPart/DisplayFormatRenderer'; import { FormatRenderer } from '../format/utils/FormatRenderer'; import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlock } from 'roosterjs-content-model-core'; +import { hasSelectionInBlock } from 'roosterjs-content-model-dom'; import { SegmentFormatView } from '../format/SegmentFormatView'; import { SizeFormatRenderers } from '../format/formatPart/SizeFormatRenderers'; import { diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx index 89a85101f9c..aca74a1ac53 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelGeneralView.tsx @@ -3,7 +3,7 @@ import { BlockGroupContentView } from './BlockGroupContentView'; import { ContentModelCodeView } from './ContentModelCodeView'; import { ContentModelLinkView } from './ContentModelLinkView'; import { ContentModelView } from '../ContentModelView'; -import { hasSelectionInBlock } from 'roosterjs-content-model-core'; +import { hasSelectionInBlock } from 'roosterjs-content-model-dom'; import { SegmentFormatView } from '../format/SegmentFormatView'; import { ContentModelGeneralBlock, diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx index 4d2db4664d1..56389746bda 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelImageView.tsx @@ -13,7 +13,7 @@ import { MetadataView } from '../format/MetadataView'; import { PaddingFormatRenderer } from '../format/formatPart/PaddingFormatRenderer'; import { SegmentFormatView } from '../format/SegmentFormatView'; import { SizeFormatRenderers } from '../format/formatPart/SizeFormatRenderers'; -import { updateImageMetadata } from 'roosterjs-content-model-core'; +import { updateImageMetadata } from 'roosterjs-content-model-dom'; import { useProperty } from '../../hooks/useProperty'; const styles = require('./ContentModelImageView.scss'); diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx index 5f138e0b185..37b4949f45f 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListItemView.tsx @@ -7,7 +7,7 @@ import { FontFamilyFormatRenderer } from '../format/formatPart/FontFamilyFormatR import { FontSizeFormatRenderer } from '../format/formatPart/FontSizeFormatRenderer'; import { FormatRenderer } from '../format/utils/FormatRenderer'; import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlockGroup } from 'roosterjs-content-model-core'; +import { hasSelectionInBlockGroup } from 'roosterjs-content-model-dom'; import { LineHeightFormatRenderer } from '../format/formatPart/LineHeightFormatRenderer'; import { MarginFormatRenderer } from '../format/formatPart/MarginFormatRenderer'; import { TextAlignFormatRenderer } from '../format/formatPart/TextAlignFormatRenderer'; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx index e0c3523979a..3560fa1791e 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelListLevelView.tsx @@ -10,7 +10,7 @@ import { MarginFormatRenderer } from '../format/formatPart/MarginFormatRenderer' import { MetadataView } from '../format/MetadataView'; import { PaddingFormatRenderer } from '../format/formatPart/PaddingFormatRenderer'; import { TextAlignFormatRenderer } from '../format/formatPart/TextAlignFormatRenderer'; -import { updateListMetadata } from 'roosterjs-content-model-core'; +import { updateListMetadata } from 'roosterjs-content-model-dom'; import { useProperty } from '../../hooks/useProperty'; import { ContentModelListItemLevelFormat, diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx index 36a626bf5be..b9c358cb08d 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelParagraphView.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { BlockFormatView } from '../format/BlockFormatView'; import { ContentModelSegmentView } from './ContentModelSegmentView'; import { ContentModelView } from '../ContentModelView'; -import { hasSelectionInBlock } from 'roosterjs-content-model-core'; +import { hasSelectionInBlock } from 'roosterjs-content-model-dom'; import { SegmentFormatView } from '../format/SegmentFormatView'; import { useProperty } from '../../hooks/useProperty'; import { diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx index 3c9987da8c2..af42966d543 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableCellView.tsx @@ -8,7 +8,7 @@ import { ContentModelView } from '../ContentModelView'; import { DirectionFormatRenderer } from '../format/formatPart/DirectionFormatRenderer'; import { FormatRenderer } from '../format/utils/FormatRenderer'; import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlockGroup, updateTableCellMetadata } from 'roosterjs-content-model-core'; +import { hasSelectionInBlockGroup, updateTableCellMetadata } from 'roosterjs-content-model-dom'; import { HtmlAlignFormatRenderer } from '../format/formatPart/HtmlAlignFormatRenderer'; import { MetadataView } from '../format/MetadataView'; import { PaddingFormatRenderer } from '../format/formatPart/PaddingFormatRenderer'; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx index 4e025ac61e0..599e47535c2 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableRowView.tsx @@ -5,7 +5,7 @@ import { ContentModelBlockGroupView } from './ContentModelBlockGroupView'; import { ContentModelView } from '../ContentModelView'; import { FormatRenderer } from '../format/utils/FormatRenderer'; import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlockGroup } from 'roosterjs-content-model-core'; +import { hasSelectionInBlockGroup } from 'roosterjs-content-model-dom'; import { useProperty } from '../../hooks/useProperty'; const styles = require('./ContentModelTableRowView.scss'); diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx index 94944051fa4..41394a16d0e 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx @@ -8,14 +8,13 @@ import { ContentModelView } from '../ContentModelView'; import { DisplayFormatRenderer } from '../format/formatPart/DisplayFormatRenderer'; import { FormatRenderer } from '../format/utils/FormatRenderer'; import { FormatView } from '../format/FormatView'; -import { hasSelectionInBlock } from 'roosterjs-content-model-core'; +import { hasSelectionInBlock, updateTableMetadata } from 'roosterjs-content-model-dom'; import { IdFormatRenderer } from '../format/formatPart/IdFormatRenderer'; import { MarginFormatRenderer } from '../format/formatPart/MarginFormatRenderer'; import { MetadataView } from '../format/MetadataView'; import { SpacingFormatRenderer } from '../format/formatPart/SpacingFormatRenderer'; import { TableLayoutFormatRenderer } from '../format/formatPart/TableLayoutFormatRenderer'; import { TableMetadataFormatRenders } from '../format/formatPart/TableMetadataFormatRenders'; -import { updateTableMetadata } from 'roosterjs-content-model-core'; import { useProperty } from '../../hooks/useProperty'; const styles = require('./ContentModelTableView.scss'); diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts index 871a524ace4..c14132c0b2f 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts @@ -12,16 +12,16 @@ const initialState: OptionState = { shortcut: true, tableEdit: true, contextMenu: true, + watermark: true, emoji: true, pasteOption: true, sampleEntity: true, + markdown: true, // Legacy plugins contentEdit: false, hyperlink: false, - watermark: false, imageEdit: false, - tableCellSelection: true, customReplace: false, announce: false, }, @@ -32,7 +32,7 @@ const initialState: OptionState = { forcePreserveRatio: false, applyChangesOnMouseUp: false, isRtl: false, - cacheModel: true, + disableCache: false, tableFeaturesContainerSelector: '#' + 'EditorContainer', allowExcelNoBorderTable: false, imageMenu: true, diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts b/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts index 2fc3eb9767d..2d0bd120033 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts @@ -5,9 +5,7 @@ import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; export interface LegacyPluginList { contentEdit: boolean; hyperlink: boolean; - watermark: boolean; imageEdit: boolean; - tableCellSelection: boolean; customReplace: boolean; announce: boolean; } @@ -19,9 +17,11 @@ export interface NewPluginList { shortcut: boolean; tableEdit: boolean; contextMenu: boolean; + watermark: boolean; emoji: boolean; pasteOption: boolean; sampleEntity: boolean; + markdown: boolean; } export interface BuildInPluginList extends LegacyPluginList, NewPluginList {} @@ -34,18 +34,18 @@ export interface OptionState { listMenu: boolean; tableMenu: boolean; imageMenu: boolean; + watermarkText: string; // Legacy plugin options contentEditFeatures: ContentEditFeatureSettings; defaultFormat: ContentModelSegmentFormat; linkTitle: string; - watermarkText: string; forcePreserveRatio: boolean; tableFeaturesContainerSelector: string; // Editor options isRtl: boolean; - cacheModel: boolean; + disableCache: boolean; applyChangesOnMouseUp: boolean; } diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx index cb728ff3eb2..54fba5e2795 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx @@ -21,17 +21,16 @@ const htmlButtons = '\n' + '\n'; '\n'; +const jsCode = '\n'; const legacyJsCode = - '\n'; -const jsCode = - '\n'; + '\n\n'; const htmlEnd = '\n' + ''; export class OptionsPane extends React.Component { private exportForm = React.createRef(); private exportData = React.createRef(); private rtl = React.createRef(); - private cacheModel = React.createRef(); + private disableCache = React.createRef(); constructor(props: OptionPaneProps) { super(props); @@ -79,13 +78,13 @@ export class OptionsPane extends React.Component {
- +

@@ -133,7 +132,7 @@ export class OptionsPane extends React.Component { forcePreserveRatio: this.state.forcePreserveRatio, applyChangesOnMouseUp: this.state.applyChangesOnMouseUp, isRtl: this.state.isRtl, - cacheModel: this.state.cacheModel, + disableCache: this.state.disableCache, tableFeaturesContainerSelector: this.state.tableFeaturesContainerSelector, allowExcelNoBorderTable: this.state.allowExcelNoBorderTable, listMenu: this.state.listMenu, @@ -175,13 +174,13 @@ export class OptionsPane extends React.Component { private onToggleCacheModel = () => { this.resetState(state => { - state.cacheModel = this.cacheModel.current.checked; + state.disableCache = this.disableCache.current.checked; }, true); }; private getHtml(requireLegacyCode: boolean) { - return `${htmlStart}${htmlButtons}${ + return `${htmlStart}${htmlButtons}${jsCode}${ requireLegacyCode ? legacyJsCode : '' - }${jsCode}${htmlEnd}`; + }${htmlEnd}`; } } diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx index ebc2503dab6..edf89de3182 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx +++ b/demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx @@ -104,7 +104,6 @@ abstract class PluginsBase extends Re export class LegacyPlugins extends PluginsBase { private linkTitle = React.createRef(); - private watermarkText = React.createRef(); private forcePreserveRatio = React.createRef(); render() { @@ -130,17 +129,6 @@ export class LegacyPlugins extends PluginsBase { (state, value) => (state.linkTitle = value) ) )} - {this.renderPluginItem( - 'watermark', - 'Watermark Plugin', - this.renderInputBox( - 'Watermark text: ', - this.watermarkText, - this.props.state.watermarkText, - '', - (state, value) => (state.watermarkText = value) - ) - )} {this.renderPluginItem( 'imageEdit', 'Image Edit Plugin', @@ -152,7 +140,6 @@ export class LegacyPlugins extends PluginsBase { ) )} {this.renderPluginItem('customReplace', 'Custom Replace Plugin (autocomplete)')} - {this.renderPluginItem('tableCellSelection', 'Table Cell Selection')} {this.renderPluginItem('announce', 'Announce')} @@ -165,6 +152,7 @@ export class Plugins extends PluginsBase { private listMenu = React.createRef(); private tableMenu = React.createRef(); private imageMenu = React.createRef(); + private watermarkText = React.createRef(); render(): JSX.Element { return ( @@ -208,6 +196,17 @@ export class Plugins extends PluginsBase { )} )} + {this.renderPluginItem( + 'watermark', + 'Watermark Plugin', + this.renderInputBox( + 'Watermark text: ', + this.watermarkText, + this.props.state.watermarkText, + '', + (state, value) => (state.watermarkText = value) + ) + )} {this.renderPluginItem('emoji', 'Emoji')} {this.renderPluginItem('pasteOption', 'PasteOptions')} {this.renderPluginItem('sampleEntity', 'SampleEntity')} diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts index 4b7dfc326e5..b800b396114 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ButtonsCode.ts @@ -2,14 +2,14 @@ import { CodeElement } from './CodeElement'; import { getObjectKeys } from 'roosterjs-content-model-dom'; const codeMap: { [id: string]: string } = { - buttonB: 'roosterjsContentModel.toggleBold(editor)', - buttonI: 'roosterjsContentModel.toggleItalic(editor)', - buttonU: 'roosterjsContentModel.toggleUnderline(editor)', - buttonBullet: 'roosterjsContentModel.toggleBullet(editor)', - buttonNumbering: 'roosterjsContentModel.toggleNumbering(editor)', - buttonUndo: 'roosterjsContentModel.undo(editor)', - buttonRedo: 'roosterjsContentModel.redo(editor)', - buttonTable: 'roosterjsContentModel.insertTable(editor, 3, 3)', + buttonB: 'roosterjs.toggleBold(editor)', + buttonI: 'roosterjs.toggleItalic(editor)', + buttonU: 'roosterjs.toggleUnderline(editor)', + buttonBullet: 'roosterjs.toggleBullet(editor)', + buttonNumbering: 'roosterjs.toggleNumbering(editor)', + buttonUndo: 'roosterjs.undo(editor)', + buttonRedo: 'roosterjs.redo(editor)', + buttonTable: 'roosterjs.insertTable(editor, 3, 3)', buttonDark: 'editor.setDarkModeState(!editor.isDarkMode())', }; diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts index bf5cf63e53d..2d4f017623a 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/ContentEditCode.ts @@ -11,6 +11,6 @@ export class ContentEditCode extends CodeElement { } getCode() { - return 'new roosterjs.ContentEdit(' + this.features.getCode() + ')'; + return 'new roosterjsLegacy.ContentEdit(' + this.features.getCode() + ')'; } } diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts index 5b1c5818511..c103657ae82 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/EditorCode.ts @@ -23,12 +23,12 @@ export class EditorCode extends CodeElement { } requireLegacyCode() { - return this.legacyPlugins.getPluginCount() > 0 || !!this.darkMode; + return this.legacyPlugins.getPluginCount() > 0; } getCode() { let defaultFormat = this.defaultFormat.getCode(); - let code = "let contentDiv = document.getElementById('contentDiv') as HTMLDivElement;\n"; + let code = "let contentDiv = document.getElementById('contentDiv');\n"; let darkMode = this.darkMode.getCode(); const hasLegacyPlugin = this.legacyPlugins.getPluginCount() > 0; @@ -36,14 +36,14 @@ export class EditorCode extends CodeElement { code += `let plugins = ${this.plugins.getCode()};\n`; code += hasLegacyPlugin ? `let legacyPlugins = ${this.legacyPlugins.getCode()};\n` : ''; code += defaultFormat ? `let defaultSegmentFormat = ${defaultFormat};\n` : ''; - code += 'let options: roosterjs.EditorOptions = {\n'; + code += 'let options = {\n'; code += this.indent('plugins: plugins,\n'); code += hasLegacyPlugin ? this.indent('legacyPlugins: legacyPlugins,\n') : ''; code += defaultFormat ? this.indent('defaultSegmentFormat: defaultSegmentFormat,\n') : ''; - code += darkMode ? this.indent(`getDarkColor: ${darkMode},\n`) : ''; + code += this.indent(`getDarkColor: ${darkMode},\n`); code += '};\n'; code += `let editor = new ${ - hasLegacyPlugin ? 'roosterjs.EditorAdapter' : 'roosterjsContentModel.Editor' + hasLegacyPlugin ? 'roosterjsAdapter.EditorAdapter' : 'roosterjs.Editor' }(contentDiv, options);\n`; code += this.buttons ? this.buttons.getCode() : ''; diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts index e4361c4f7c0..8f36f804cc6 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/HyperLinkCode.ts @@ -7,7 +7,7 @@ export class HyperLinkCode extends CodeElement { } getCode() { - return 'new roosterjs.HyperLink(' + this.getLinkCallback() + ')'; + return 'new roosterjsLegacy.HyperLink(' + this.getLinkCallback() + ')'; } private getLinkCallback() { diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts index 7b298b17e6b..e4a9e64db78 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/PluginsCode.ts @@ -9,9 +9,9 @@ import { EditPluginCode, ImageEditCode, PastePluginCode, - TableCellSelectionCode, TableEditPluginCode, ShortcutPluginCode, + MarkdownPluginCode, } from './SimplePluginCode'; export class PluginsCodeBase extends CodeElement { @@ -44,6 +44,8 @@ export class PluginsCode extends PluginsCodeBase { pluginList.paste && new PastePluginCode(), pluginList.tableEdit && new TableEditPluginCode(), pluginList.shortcut && new ShortcutPluginCode(), + pluginList.watermark && new WatermarkCode(state.watermarkText), + pluginList.markdown && new MarkdownPluginCode(), ]); } } @@ -55,10 +57,8 @@ export class LegacyPluginCode extends PluginsCodeBase { const plugins: CodeElement[] = [ pluginList.contentEdit && new ContentEditCode(state.contentEditFeatures), pluginList.hyperlink && new HyperLinkCode(state.linkTitle), - pluginList.watermark && new WatermarkCode(state.watermarkText), pluginList.imageEdit && new ImageEditCode(), pluginList.customReplace && new CustomReplaceCode(), - pluginList.tableCellSelection && new TableCellSelectionCode(), ]; super(plugins); diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts index 5dc6d8d2f36..482c1ff4e3c 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/SimplePluginCode.ts @@ -1,7 +1,7 @@ import { CodeElement } from './CodeElement'; class SimplePluginCode extends CodeElement { - constructor(private name: string, private namespace: string = 'roosterjsContentModel') { + constructor(private name: string, private namespace: string = 'roosterjs') { super(); } @@ -42,18 +42,18 @@ export class TableEditPluginCode extends SimplePluginCode { export class ImageEditCode extends SimplePluginCode { constructor() { - super('ImageEdit', 'roosterjs'); + super('ImageEdit', 'roosterjsLegacy'); } } export class CustomReplaceCode extends SimplePluginCode { constructor() { - super('CustomReplace', 'roosterjs'); + super('CustomReplace', 'roosterjsLegacy'); } } -export class TableCellSelectionCode extends SimplePluginCode { +export class MarkdownPluginCode extends SimplePluginCode { constructor() { - super('TableCellSelection', 'roosterjs'); + super('MarkdownPlugin'); } } diff --git a/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts b/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts index fbc1a9763c8..9a7243ccb1b 100644 --- a/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts +++ b/demo/scripts/controlsV2/sidePane/editorOptions/codes/WatermarkCode.ts @@ -6,6 +6,6 @@ export class WatermarkCode extends CodeElement { } getCode() { - return `new roosterjs.Watermark('${this.encode(this.watermarkText)}')`; + return `new roosterjs.WatermarkPlugin('${this.encode(this.watermarkText)}')`; } } diff --git a/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx index 5e14ff41c6b..b4c551699ce 100644 --- a/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx +++ b/demo/scripts/controlsV2/sidePane/eventViewer/EventViewPane.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import { getObjectKeys } from 'roosterjs-content-model-dom'; -import { readFile } from 'roosterjs-content-model-core'; +import { getObjectKeys, readFile } from 'roosterjs-content-model-dom'; import { SidePaneElementProps } from '../SidePaneElement'; import type { PluginEvent } from 'roosterjs-content-model-types'; diff --git a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx index d9ef783395f..c501957cc86 100644 --- a/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx +++ b/demo/scripts/controlsV2/sidePane/formatState/FormatStatePane.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; +import { MainPane } from '../../mainPane/MainPane'; import { SidePaneElementProps } from '../SidePaneElement'; import { ContentModelFormatState, EditorEnvironment, + ImageFormatState, TableMetadataFormat, } from 'roosterjs-content-model-types'; @@ -37,9 +39,13 @@ export default class FormatStatePane extends React.Component< render() { const { format, x, y } = this.state; const { isMac, isAndroid, isSafari, isMobileOrTablet } = this.props.env ?? {}; + const mpState = MainPane.getInstance(); const TableFormat = () => { const tableFormat = format.tableFormat; + const tableBorder = mpState.getTableBorder(); + const tableBorderFormat = + tableBorder.style + ' ' + tableBorder.width + ' ' + tableBorder.color; if (!tableFormat) { return <>; } @@ -47,9 +53,11 @@ export default class FormatStatePane extends React.Component< (key: keyof TableMetadataFormat, index: number, array) => { const rowStyle: React.CSSProperties = index == 0 - ? { borderTop: 'solid' } + ? { + borderTop: tableBorderFormat, + } : index == array.length - 1 - ? { borderBottom: 'solid' } + ? { borderBottom: tableBorderFormat } : {}; return ( @@ -61,9 +69,32 @@ export default class FormatStatePane extends React.Component< ); return tableFromatRows; }; + + const ImageFormat = () => { + const imageFormat = format.imageFormat; + if (!imageFormat) { + return <>; + } + const imageFormatRows = Object.keys(imageFormat).map( + (key: keyof ImageFormatState, index: number, array) => { + return ( + + {key} + {String(imageFormat[key])} + + ); + } + ); + return imageFormatRows; + }; + return format ? ( + + + + {ImageFormat()}