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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -552,6 +555,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
this.contentModelPanePlugin,
this.presetPlugin,
this.markdownPanePlugin,
this.textUtilitiesPanePlugin,
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
157 changes: 157 additions & 0 deletions demo/scripts/controlsV2/sidePane/TextUtilities/TextUtilitiesPane.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLTextAreaElement>();

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 (
<div className={styles.container}>
<p>Enter text to transform and display in the editor.</p>
<textarea className={styles.textArea} title="Text input" ref={this.text} />
{this.state.errorMessage ? (
<div className={styles.errorMessage} role="alert">
{this.state.errorMessage}
</div>
) : null}
<div className={styles.buttons}>
<button type="button" onClick={this.beautifyJson}>
Beautify JSON
</button>
<button type="button" onClick={this.encodeUriComponent}>
Encode URI component
</button>
<button type="button" onClick={this.decodeUriComponent}>
Decode URI component
</button>
<button type="button" onClick={this.encodeBase64}>
Encode as Base64
</button>
<button type="button" onClick={this.decodeBase64}>
Decode from Base64
</button>
<button type="button" onClick={this.clear}>
Clear
</button>
<button type="button" onClick={this.clearEditor}>
Clear editor
</button>
</div>
</div>
);
}
}

function encodeTextToBase64(value: string): string {
const encodedValue = encodeURIComponent(value);
let binaryValue = '';

for (let i = 0; i < encodedValue.length; i++) {
if (encodedValue[i] == '%') {
binaryValue += String.fromCharCode(parseInt(encodedValue.substr(i + 1, 2), 16));
i += 2;
} else {
binaryValue += encodedValue[i];
}
}

return btoa(binaryValue);
}

function decodeBase64ToText(value: string): string {
const binaryValue = atob(value);
let encodedValue = '';

for (let i = 0; i < binaryValue.length; i++) {
encodedValue += '%' + ('0' + binaryValue.charCodeAt(i).toString(16)).slice(-2);
}

return decodeURIComponent(encodedValue);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import TextUtilitiesPane from './TextUtilitiesPane';
import { IEditor } from 'roosterjs-content-model-types';
import { SidePaneElementProps } from '../SidePaneElement';
import { SidePanePluginImpl } from '../SidePanePluginImpl';

export interface TextUtilitiesPaneProps extends SidePaneElementProps {
getEditor: () => IEditor;
}

export class TextUtilitiesPanePlugin extends SidePanePluginImpl<
TextUtilitiesPane,
TextUtilitiesPaneProps
> {
constructor() {
super(TextUtilitiesPane, 'textUtilities', 'Text Utilities');
}

getComponentProps(base: TextUtilitiesPaneProps) {
return {
...base,
getEditor: () => this.editor,
};
}
}
Loading