Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ type YooptaEditor = {
style?: number | string;
/* Id for your editor instance. Can be useful for multiple editors */
id?: number | string;
/* Custom translations for the editor. Allows you to override current translations or add a new whole language. */
translations?: Translations;
};
```

Expand Down
2 changes: 2 additions & 0 deletions packages/core/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ type Props = {
onChange?: (value: YooptaContentValue, options: YooptaOnChangeOptions) => void;
/* Path change handler */
onPathChange?: (path: YooptaPath) => void;
/* Custom translations for the editor. Allows you to override current translations or add a new whole language. */
translations?: Translations;
};
```

Expand Down
12 changes: 5 additions & 7 deletions packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ const BlockOptionsSeparator = ({ className = '' }: BlockOptionsSeparatorProps) =
);

export type BlockOptionsProps = {
refs: any;
isOpen: boolean;
onClose: () => void;
refs: any;
style: CSSProperties;
children?: React.ReactNode;
actions?: ['delete', 'duplicate', 'turnInto', 'copy'] | null;
Expand Down Expand Up @@ -79,7 +79,6 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS,
const onDuplicate = () => {
// [TEST]
if (typeof editor.path.current !== 'number') return;

editor.duplicateBlock({ original: { path: editor.path.current }, focus: true });

onClose();
Expand Down Expand Up @@ -116,13 +115,13 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS,
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDelete}>
<TrashIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Delete
{editor.getLabelText('editor.blockOptions.delete') || 'Delete'}
</button>
</BlockOptionsMenuItem>
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onDuplicate}>
<CopyIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Duplicate
{editor.getLabelText('editor.blockOptions.duplicate') || 'Duplicate'}
</button>
</BlockOptionsMenuItem>
{!!ActionMenu && !isVoidElement && !editor.blocks[currentBlock?.type || '']?.hasCustomEditor && (
Expand All @@ -131,7 +130,6 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS,
<Portal id="yoo-block-options-portal">
<Overlay lockScroll className="yoo-editor-z-[100]" onClick={() => setIsActionMenuOpen(false)}>
<div style={actionMenuStyles} ref={actionMenuRefs.setFloating}>
{/* @ts-ignore - fixme */}
<ActionMenu {...actionMenuRenderProps} />
</div>
</Overlay>
Expand All @@ -144,14 +142,14 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS,
onClick={() => setIsActionMenuOpen((open) => !open)}
>
<TurnIcon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Turn into
{editor.getLabelText('editor.blockOptions.turnInto') || 'Turn into'}
</button>
</BlockOptionsMenuItem>
)}
<BlockOptionsMenuItem>
<button type="button" className="yoopta-block-options-button" onClick={onCopy}>
<Link2Icon className="yoo-editor-w-4 yoo-editor-h-4 yoo-editor-mr-2" />
Copy link to block
{editor.getLabelText('editor.blockOptions.copyBlockLink') || 'Copy link to block'}
</button>
</BlockOptionsMenuItem>
</BlockOptionsMenuGroup>
Expand Down
12 changes: 7 additions & 5 deletions packages/core/editor/src/UI/BlockOptions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ export function buildActionMenuRenderProps({ editor, view, onClose, mode = 'togg
items = items.filter((type) => filterToggleActions(editor, type));
}

return items.map((action) => {
const title = editor.blocks[action].options?.display?.title || action;
const description = editor.blocks[action].options?.display?.description;
const icon = editor.blocks[action].options?.display?.icon;
return { type: action, title, description, icon };
return items.map((type) => {
const title =
editor.getLabelText(`plugins.${type}.display.title`) || editor.blocks[type].options?.display?.title || type;
const description =
editor.getLabelText(`plugins.${type}.display.description`) || editor.blocks[type].options?.display?.description;
const icon = editor.blocks[type].options?.display?.icon;
return { type: type, title, description, icon };
});
};

Expand Down
5 changes: 4 additions & 1 deletion packages/core/editor/src/YooptaEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const YooptaEditor = ({
}, [marksProps]);

const plugins = useMemo(() => {
return pluginsProps.map((plugin) => plugin.getPlugin as Plugin<Record<string, SlateElement>>);
return pluginsProps.map((pluginInstance) => {
const plugin = pluginInstance.getPlugin as Plugin<Record<string, SlateElement>>;
return plugin;
});
}, [pluginsProps]);

const [editorState, setEditorState] = useState<EditorState>(() => {
Expand Down
43 changes: 43 additions & 0 deletions packages/core/editor/src/constants/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { RecursiveDotNotation } from '../types/i18n';

export const DEFAULT_LABEL_TEXT_MAP = {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

// editor: {
// blockOptions: {
// delete: 'Delete',
// duplicate: 'Duplicate',
// turnInto: 'Turn into',
// copyBlockLink: 'Copy link to block',
// },
// placeholder: `Type '/' for commands`,
// },
// tools: {
// toolbar: {
// highlightColor: {
// text: 'Text',
// background: 'Background',
// customColor: 'Custom color',
// },
// linkTitle: 'Link',
// },
// actionMenu: {
// noActionsAvailable: 'No actions available',
// },
// link: {
// title: 'Link title',
// url: 'Link URL',
// target: 'Link target',
// rel: 'Link rel',
// update: 'Update',
// add: 'Add',
// delete: 'Delete link',
// additionalProps: 'Additional props',
// targetPlaceholder: 'Edit link target',
// relPlaceholder: 'Edit link rel',
// titlePlaceholder: 'Edit link title',
// urlPlaceholder: 'Edit link URL',
// },
// },
};

// [TODO] - add types
export type LabelKeys = {};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add types

17 changes: 17 additions & 0 deletions packages/core/editor/src/editor/i18n/getLabelText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DEFAULT_LABEL_TEXT_MAP, LabelKeys } from '../../constants/labels';
import { YooEditor } from '../types';

function getNestedValue(obj: any, path: string[]): string | undefined {
return path.reduce((acc, part) => {
if (acc && typeof acc === 'object' && part in acc) {
return acc[part];
}
return undefined;
}, obj);
}

export function getLabelText(editor: YooEditor, key: LabelKeys) {
const keyParts = key.split('.');
const currentLangValue = getNestedValue(DEFAULT_LABEL_TEXT_MAP, keyParts);
return currentLangValue || '';
}
3 changes: 3 additions & 0 deletions packages/core/editor/src/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { mergeBlock } from './blocks/mergeBlock';
import { UndoRedoOptions, YooptaHistory } from './core/history';
import EventEmitter from 'eventemitter3';
import { getEmail, EmailTemplateOptions } from '../parsers/getEmail';
import { getLabelText } from './i18n/getLabelText';

const eventEmitter = new EventEmitter();

Expand Down Expand Up @@ -68,6 +69,8 @@ export function createYooptaEditor(): YooEditor {
applyTransforms: (operations, ...args) => applyTransforms(editor, operations, ...args),
batchOperations: (callback) => batchOperations(editor, callback),

getLabelText: (key) => getLabelText(editor, key),

on: (event, callback) => Events.on(event, callback),
off: (event, callback) => Events.off(event, callback),
emit: (event, ...args) => Events.emit(event, ...args),
Expand Down
4 changes: 4 additions & 0 deletions packages/core/editor/src/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { getHTML } from '../parsers/getHTML';
import { getMarkdown } from '../parsers/getMarkdown';
import { getPlainText } from '../parsers/getPlainText';
import { getEmail } from '../parsers/getEmail';
import { getLabelText } from './i18n/getLabelText';

export type YooptaBlockData<T = Descendant | SlateElement> = {
id: string;
Expand Down Expand Up @@ -126,6 +127,9 @@ export type YooEditor = {
applyTransforms: WithoutFirstArg<typeof applyTransforms>;
batchOperations: (fn: () => void) => void;

// default labels. get label by current language. default: 'en'
getLabelText: WithoutFirstArg<typeof getLabelText>;

// events handlers
on: <K extends keyof YooptaEventsMap>(event: K, fn: (payload: YooptaEventsMap[K]) => void) => void;
once: <K extends keyof YooptaEventsMap>(event: K, fn: (payload: YooptaEventsMap[K]) => void) => void;
Expand Down
1 change: 1 addition & 0 deletions packages/core/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
useYooptaReadOnly,
useYooptaPluginOptions,
} from './contexts/YooptaContext/YooptaContext';
export { useTranslation } from './i18n/hooks/useTranslation';
import { YooptaEditor, type YooptaEditorProps, type YooptaOnChangeOptions } from './YooptaEditor';
export { deserializeHTML } from './parsers/deserializeHTML';
export { type EmailTemplateOptions } from './parsers/getEmail';
Expand Down
11 changes: 8 additions & 3 deletions packages/core/editor/src/plugins/SlateEditorComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { deserializeHTML } from '../parsers/deserializeHTML';
import { useEventHandlers, useSlateEditor } from './hooks';
import { SlateElement } from '../editor/types';

type Props<TElementMap extends Record<string, SlateElement>, TOptions> = Plugin<TElementMap, TOptions> & {
type Props<TElementMap extends Record<string, SlateElement>, TOptions> = Omit<
Plugin<TElementMap, TOptions>,
'translations'
> & {
id: string;
marks?: YooptaMark<any>[];
options: Plugin<TElementMap, TOptions>['options'];
Expand Down Expand Up @@ -44,7 +47,7 @@ const SlateEditorComponent = <TElementMap extends Record<string, SlateElement>,
events,
options,
extensions: withExtensions,
placeholder = `Type '/' for commands`,
placeholder = 'editor.placeholder',
}: Props<TElementMap, TOptions>) => {
const editor = useYooptaEditor();
const block = useBlockData(id);
Expand Down Expand Up @@ -111,8 +114,10 @@ const SlateEditorComponent = <TElementMap extends Record<string, SlateElement>,
const isParentElementVoid = props.children?.props?.parent?.props?.nodeType === 'void';
const showPlaceholder = !isParentElementVoid && isCurrentPath && leaf.withPlaceholder;

const placeholderText = editor.getLabelText('editor.placeholder') || placeholder;

return (
<TextLeaf attributes={attributes} placeholder={showPlaceholder ? placeholder : undefined}>
<TextLeaf attributes={attributes} placeholder={showPlaceholder ? placeholderText : undefined}>
{children}
</TextLeaf>
);
Expand Down
11 changes: 11 additions & 0 deletions packages/core/editor/src/types/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type RecursiveDotNotation<T, P extends string = ''> = T extends object
? {
[K in keyof T]: T[K] extends object
? RecursiveDotNotation<T[K], P extends '' ? `${K & string}` : `${P}.${K & string}`>
: T[K] extends string
? P extends ''
? `${K & string}`
: `${P}.${K & string}`
: never;
}[keyof T]
: P;
126 changes: 126 additions & 0 deletions packages/core/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Exports
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


Exports is core package for exporting/importing yoopta content in different formats
The package `@yoopta/exports` supports exporting/importing in the next formats:

- HTML
- Markdown
- Plain text

### Installation

```bash
yarn add @yoopta/exports
```

### Usage

HTML exports/imports example

```jsx
import { html } from '@yoopta/exports';

const Editor = () => {
const editor = useMemo(() => createYooptaEditor(), []);

// from html to @yoopta content
const deserializeHTML = () => {
const htmlString = '<h1>First title</h1>';
const content = html.deserialize(editor, htmlString);

editor.setEditorValue(content);
};

// from @yoopta content to html string
const serializeHTML = () => {
const data = editor.getEditorValue();
const htmlString = html.serialize(editor, data);
console.log('html string', htmlString);
};

return (
<div>
<button onClick={deserializeHTML}>Deserialize from html to content</button>
<button onClick={serializeHTML}>Serialize from content to html</button>

<YooptaEditor editor={editor} plugins={plugins} />
</div>
);
};
```

---

Markdown exports/imports example

```jsx
import { markdown } from '@yoopta/exports';

const Editor = () => {
const editor = useMemo(() => createYooptaEditor(), []);

// from markdown to @yoopta content
const deserializeMarkdown = () => {
const markdownString = '# First title';
const value = markdown.deserialize(editor, markdownString);

editor.setEditorValue(value);
};

// from @yoopta content to markdown string
const serializeMarkdown = () => {
const data = editor.getEditorValue();
const markdownString = markdown.serialize(editor, data);
console.log('markdown string', markdownString);
};

return (
<div>
<button onClick={deserializeMarkdown}>Deserialize from markdown to content</button>
<button onClick={serializeMarkdown}>Serialize from content to markdown</button>

<YooptaEditor editor={editor} plugins={plugins} />
</div>
);
};
```

Plain text exports/imports example

```jsx
import { plainText } from '@yoopta/exports';

const Editor = () => {
const editor = useMemo(() => createYooptaEditor(), []);

// from plain text to @yoopta content
const deserializeText = () => {
const textString = '# First title';
const value = plainText.deserialize(editor, textString);

editor.setEditorValue(value);
};

// from @yoopta content to plain text string
const serializeText = () => {
const data = editor.getEditorValue();
const textString = plainText.serialize(editor, data);
console.log('plain text string', textString);
};

return (
<div>
<button onClick={deserializeText}>Deserialize from plain text to content</button>
<button onClick={serializeText}>Serialize from content to plain text</button>

<YooptaEditor editor={editor} plugins={plugins} />
</div>
);
};
```

Examples

- Page - [https://yoopta.dev/examples/withExports](https://yoopta.dev/examples/withExports)
- Example with HTML - [https://yoopta.dev/examples/withExports/html](https://yoopta.dev/examples/withExports/html)
- Example with Markdown - [https://yoopta.dev/examples/withExports/markdown](https://yoopta.dev/examples/withExports/markdown)
Loading