diff --git a/demo/scripts/controlsV2/mainPane/MainPane.tsx b/demo/scripts/controlsV2/mainPane/MainPane.tsx index e8a3945195a..5a9838f4d4b 100644 --- a/demo/scripts/controlsV2/mainPane/MainPane.tsx +++ b/demo/scripts/controlsV2/mainPane/MainPane.tsx @@ -20,6 +20,7 @@ import { getTabs, tabNames } from '../tabs/getTabs'; import { getTheme } from '../theme/themes'; import { MarkdownPanePlugin } from '../sidePane/MarkdownPane/MarkdownPanePlugin'; import { MarkdownPastePlugin } from 'roosterjs-content-model-markdown'; +import { TextUtilitiesPanePlugin } from '../sidePane/TextUtilities/TextUtilitiesPanePlugin'; import { OptionState, UrlPlaceholder } from '../sidePane/editorOptions/OptionState'; import { popoutButton } from '../demoButtons/popoutButton'; import { PresetPlugin } from '../sidePane/presets/PresetPlugin'; @@ -116,6 +117,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { private samplePickerPlugin: SamplePickerPlugin; private snapshots: Snapshots; private markdownPanePlugin: MarkdownPanePlugin; + private textUtilitiesPanePlugin: TextUtilitiesPanePlugin; private findReplacePlugin: FindReplacePlugin; private findReplaceContext: FindReplaceContext; private imageEditPlugin: ImageEditPlugin | undefined; @@ -157,6 +159,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { this.formatPainterPlugin = new FormatPainterPlugin(); this.samplePickerPlugin = new SamplePickerPlugin(); this.markdownPanePlugin = new MarkdownPanePlugin(); + this.textUtilitiesPanePlugin = new TextUtilitiesPanePlugin(); this.findReplaceContext = createFindReplaceContext(); this.findReplacePlugin = new FindReplacePlugin(this.findReplaceContext); @@ -552,6 +555,7 @@ export class MainPane extends React.Component<{}, MainPaneState> { this.contentModelPanePlugin, this.presetPlugin, this.markdownPanePlugin, + this.textUtilitiesPanePlugin, ]; } diff --git a/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.scss b/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.scss new file mode 100644 index 00000000000..93083250624 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.scss @@ -0,0 +1,23 @@ +.container { + flex-direction: column; + display: flex; + height: 100%; +} + +.textArea { + flex: 1; + width: 100%; + box-sizing: border-box; + resize: none; +} + +.errorMessage { + color: #a80000; + margin: 4px 0; +} + +.buttons { + display: flex; + flex-wrap: wrap; + gap: 4px; +} diff --git a/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.tsx b/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.tsx new file mode 100644 index 00000000000..5dc61d53e20 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.tsx @@ -0,0 +1,157 @@ +import * as React from 'react'; +import { + createEmptyModel, + createFormatContainer, + createParagraph, + createText, +} from 'roosterjs-content-model-dom'; +import type { TextUtilitiesPaneProps } from './TextUtilitiesPanePlugin'; + +const styles = require('./TextUtilitiesPane.scss'); + +interface TextUtilitiesPaneState { + errorMessage: string; +} + +export default class TextUtilitiesPane extends React.Component< + TextUtilitiesPaneProps, + TextUtilitiesPaneState +> { + private text = React.createRef(); + + constructor(props: TextUtilitiesPaneProps) { + super(props); + this.state = { errorMessage: '' }; + } + + private beautifyJson = () => { + this.applyTransformation( + value => JSON.stringify(JSON.parse(value), null, 2), + 'Invalid JSON' + ); + }; + + private encodeUriComponent = () => { + this.applyTransformation( + value => encodeURIComponent(value), + 'Unable to encode URI component' + ); + }; + + private decodeUriComponent = () => { + this.applyTransformation(value => decodeURIComponent(value), 'Invalid URI component'); + }; + + private encodeBase64 = () => { + this.applyTransformation(value => encodeTextToBase64(value), 'Unable to encode Base64'); + }; + + private decodeBase64 = () => { + this.applyTransformation(value => decodeBase64ToText(value), 'Invalid Base64'); + }; + + private applyTransformation = (transform: (value: string) => string, fallbackError: string) => { + let result: string; + + try { + result = transform(this.text.current.value); + } catch (error) { + this.setState({ + errorMessage: error instanceof Error ? error.message : fallbackError, + }); + return; + } + + this.setEditorText(result); + }; + + private setEditorText = (text: string) => { + const container = createFormatContainer('pre'); + const paragraph = createParagraph(true); + + paragraph.segments.push(createText(text)); + container.blocks.push(paragraph); + this.props.getEditor().formatContentModel(model => { + model.blocks = [container]; + return true; + }); + this.setState({ errorMessage: '' }); + }; + + private clear = () => { + this.text.current.value = ''; + this.setState({ errorMessage: '' }); + }; + + private clearEditor = () => { + const editor = this.props.getEditor(); + editor.formatContentModel(model => { + model.blocks = createEmptyModel().blocks; + return true; + }); + }; + + render() { + return ( +
+

Enter text to transform and display in the editor.

+