Skip to content

Commit

Permalink
feat: getNodeDeserializer supports dynamic style rules + example
Browse files Browse the repository at this point in the history
  • Loading branch information
ghingis committed Jul 16, 2021
1 parent 353f21a commit d906095
Show file tree
Hide file tree
Showing 17 changed files with 551 additions and 129 deletions.
6 changes: 6 additions & 0 deletions .changeset/silly-rats-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@udecode/slate-plugins-common": patch
---

getNodeDeserializer now supports dynamic style rules by providing an asterisk instead of an exact value
documentation: adds a font color example that leverages the changed deserializer
98 changes: 98 additions & 0 deletions docs/docs/examples/font-color.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
slug: /examples/font-color
title: Font color
---

:::note

Most of the following variables are exported from the library or defined in the corresponding example.
For quick lookup, jump to the [source code](https://github.com/udecode/slate-plugins/blob/main/docs/src/live/live.tsx).

:::

The following playground shows an example how to handle dynamic marks

```ts live
() => {
let styledComponents = createSlatePluginsComponents({
...components,
[MARK_COLOR]: withMarkedProps(StyledLeaf, {
marks: [MARK_COLOR],
}),
});
styledComponents = withStyledPlaceHolders(styledComponents);

const defaultOptions = createSlatePluginsOptions();

const CopyContent = () => (
<div style={{borderBottom: '1px solid #eee', margin: '0 -16px', padding: '0 16px 16px'}}>
<span style={{color: '#f92672'}}>Copy Me in the editor</span>
</div>
);

const SltEditor = () => {
const { setSearch, plugin: searchHighlightPlugin } = useFindReplacePlugin();

const pluginsMemo = useMemo(() => {
const plugins = [
createColorPlugin(),
createReactPlugin(),
createHistoryPlugin(),
createParagraphPlugin(),
createHeadingPlugin(),
createBoldPlugin(),
createCodePlugin(),
createItalicPlugin(),
createHighlightPlugin(),
createUnderlinePlugin(),
createStrikethroughPlugin(),
createAutoformatPlugin(optionsAutoformat),
createResetNodePlugin(optionsResetBlockTypePlugin),
createSoftBreakPlugin(optionsSoftBreakPlugin),
createExitBreakPlugin(optionsExitBreakPlugin),
createNormalizeTypesPlugin({
rules: [{ path: [0], strictType: ELEMENT_H1 }],
}),
];

plugins.push(createDeserializeHTMLPlugin({ plugins }));

return plugins;
}, [options, searchHighlightPlugin]);

const initialValue = [
...initialValueBasicMarks,
{
type: ELEMENT_PARAGRAPH,
children: [
{
text: 'This text is bold, italic and underlined and colored.',
[MARK_BOLD]: true,
[MARK_ITALIC]: true,
[MARK_UNDERLINE]: true,
[MARK_COLOR]: '#f92672'
},
],
},
];

return (
<SlatePlugins
id="playground"
plugins={pluginsMemo}
components={styledComponents}
options={defaultOptions}
editableProps={editableProps}
initialValue={initialValue}
>
<HeadingToolbar>
<ToolbarColorPicker icon={<FormatColorText />} />
</HeadingToolbar>
<CopyContent />
</SlatePlugins>
);
}

return <SltEditor />;
}
```
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ module.exports = {
'examples/huge-document',
'examples/iframe',
'examples/preview-markdown',
'examples/font-color',
],
collapsed: false,
},
Expand Down
19 changes: 19 additions & 0 deletions docs/src/live/examples/font-color/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';

export const ColorPicker = ({
color,
updateColor,
}: {
color: string | undefined;
updateColor: (ev: any, colorObj: string) => void;
}) => {
return (
<div style={{ display: 'flex' }}>
<input
type="color"
onChange={(ev) => updateColor(ev, ev.target.value)}
value={color || '#000000'}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'tippy.js/animations/scale.css';
import 'tippy.js/dist/tippy.css';
import React, {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import {
getSlatePluginType,
ToolbarButton,
useStoreEditorRef,
useStoreEditorSelection,
} from '@udecode/slate-plugins';
import {
useEventEditorId,
useStoreEditorState,
} from '@udecode/slate-plugins-core';
import { BaseSelection, Transforms } from 'slate';
import { ReactEditor } from 'slate-react';
import { MARK_COLOR } from '../defaults';
import { getMark } from '../utils/getMark';
import { setMark } from '../utils/setMark';
import { ColorPicker } from './ColorPicker';
import { ToolbarDropdown } from './ToolbarDropdown';

export const ToolbarColorPicker = ({ icon }: { icon: ReactNode }) => {
const editor = useStoreEditorState(useEventEditorId('focus'));
const editorRef = useStoreEditorRef(useEventEditorId('focus'));
const selection = useStoreEditorSelection(useEventEditorId('focus'));
const type = getSlatePluginType(editor, MARK_COLOR);

const color = editorRef && getMark(editorRef, type);

const [selectedColor, setSelectedColor] = useState<string>();

const latestSelection = useRef<BaseSelection>();

const updateColor = useCallback((ev: any, colorParam: string) => {
setSelectedColor(colorParam);
}, []);

useEffect(() => {
if (selection) {
latestSelection.current = selection;
setSelectedColor(color);
}
}, [color, selection]);

return (
<ToolbarDropdown
control={
<ToolbarButton
active={!!editor?.selection && !!type && !!getMark(editor, type)}
icon={icon}
/>
}
onClose={(ev: MouseEvent) => {
if (editorRef && editor && latestSelection.current) {
ev.preventDefault();
Transforms.select(editorRef, latestSelection.current);
ReactEditor.focus(editorRef);
if (selectedColor) {
setMark(editor, type, selectedColor);
}
}
}}
>
<ColorPicker color={selectedColor || color} updateColor={updateColor} />
</ToolbarDropdown>
);
};
64 changes: 64 additions & 0 deletions docs/src/live/examples/font-color/components/ToolbarDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { ReactNode, useEffect, useState } from 'react';

export const ToolbarDropdown = ({
control,
children,
onClose,
}: {
control: ReactNode;
children: ReactNode;
onClose?: (ev: MouseEvent) => void;
}) => {
const [
referenceElement,
setReferenceElement,
] = useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null
);

const [open, setOpen] = useState(false);

useEffect(() => {
const listener = (ev: MouseEvent) => {
if (open) {
if (referenceElement && ev.composedPath().includes(referenceElement)) {
return;
}
if (popperElement && ev.composedPath().includes(popperElement)) {
return;
}

setOpen(false);
onClose && typeof onClose === 'function' && onClose(ev);
}
};
document.body.addEventListener('mousedown', listener);
return () => {
document.body.removeEventListener('mousedown', listener);
};
}, [onClose, open, popperElement, referenceElement, setOpen]);

return (
<>
<div ref={setReferenceElement} onMouseDown={() => setOpen(true)}>
{control}
</div>

<div
ref={setPopperElement}
style={{
position: 'absolute',
top: '40px',
backgroundColor: 'white',
border: '1px solid #ccc',
boxShadow: '0 1px 3px 0 #ccc',
zIndex: 1,
display: open ? 'initial' : 'none',
}}
>
{children}
</div>
</>
);
};
10 changes: 10 additions & 0 deletions docs/src/live/examples/font-color/createColorPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {getRenderLeaf} from '@udecode/slate-plugins';
import {SlatePlugin} from '@udecode/slate-plugins-core';
import {MARK_COLOR} from './defaults';
import {getColorDeserialize} from './getColorDeserialize';

export const createColorPlugin = (): SlatePlugin => ({
pluginKeys: MARK_COLOR,
renderLeaf: getRenderLeaf(MARK_COLOR),
deserialize: getColorDeserialize(),
});
1 change: 1 addition & 0 deletions docs/src/live/examples/font-color/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MARK_COLOR = 'color';
22 changes: 22 additions & 0 deletions docs/src/live/examples/font-color/getColorDeserialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Deserialize, getSlatePluginOptions,} from '@udecode/slate-plugins-core';
import {MARK_COLOR} from "./defaults";
import {getNodeDeserializer} from "@udecode/slate-plugins";

export const getColorDeserialize = (): Deserialize => (editor) => {
const options = getSlatePluginOptions(editor, MARK_COLOR);

return {
leaf: getNodeDeserializer({
type: options.type,
getNode: (element) => ({ [options.type]: element.style.color }),
rules: [
{
style: {
color: '*',
},
},
],
...options.deserialize,
}),
};
};
8 changes: 8 additions & 0 deletions docs/src/live/examples/font-color/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @file Automatically generated by barrelsby.
*/

export * from './createColorPlugin';
export * from './defaults';
export * from './getColorDeserialize';
export * from './utils/withMarkedProps';
15 changes: 15 additions & 0 deletions docs/src/live/examples/font-color/utils/getMark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BaseEditor, Editor } from 'slate';

/**
* Return the marks in the selection.
* @param editor
* @param type
*/
export const getMark = (editor: BaseEditor, type: string): any => {
if (!editor) {
return;
}

const marks = Editor.marks(editor);
return marks && marks[type];
};
35 changes: 35 additions & 0 deletions docs/src/live/examples/font-color/utils/setMark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { removeMark } from '@udecode/slate-plugins';
import { TEditor } from '@udecode/slate-plugins-core';
import castArray from 'lodash/castArray';
import { getMark } from './getMark';

/**
* Add/remove marks in the selection.
* @param editor
* @param mark mark to toggle
* @param value
* @param clear marks to clear when adding mark
*/
export const setMark = (
editor: TEditor,
mark: string,
value: any = true,
clear: string | string[] = []
) => {
if (!editor?.selection) {
return;
}

const activeMark = getMark(editor, mark);

if (activeMark) {
removeMark(editor, { key: mark });
}

const clears: string[] = castArray(clear);
clears.forEach((item) => {
removeMark(editor, { key: item });
});

editor.addMark(mark, value);
};
Loading

0 comments on commit d906095

Please sign in to comment.