Skip to content

Commit

Permalink
feat: allow to customize slate plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
macrozone committed Aug 14, 2019
1 parent aeeadae commit 89eb22b
Show file tree
Hide file tree
Showing 33 changed files with 788 additions and 591 deletions.
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
semi: true
60 changes: 58 additions & 2 deletions docs/plugins/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,64 @@ encourage using our text editing solution.

### Customize text editing

We wrote the text editing plugin in a way that allows you to customize if you want h1-h6 or lists, and you can even
create your own logic. The documentation on this topic is still work in progress.
By default we provide the following plugins:

- AlignmentPlugin: allows to align left, right center and justify,
- BlockquotePlugin: place a blockquote
- CodePlugin: place code
- EmphasizePlugin: place em tags
- HeadingsPlugin: place headings h1 - h6
- LinkPlugin: place links
- ListsPlugin: place ordered and unordered lists
- ParagraphPlugin: place paragraphs

If you only want to include some plugins, you can specify them:

```
import slate, { slatePlugins } from '@react-page/plugins-slate';
const slatePlugin = slate([
new slatePlugins.HeadingsPlugin(),
new slatePlugins.ParagraphPlugin(),
]);
const plugins: Plugins = {
content: [
slatePlugin,
// ...
],
// ...
};
const editor = new Editor({
plugins: plugins,
// pass the content states
editables: [
...content,
// creates an empty state, basically like the line above
createEmptyState(),
],
});
```

Some plugins allow further configuration. E.g. most plugins implement `getComponent` where you can override the component rendering:

```
// any custom component
const RedH1 = ({ style, ...props }: Props) => (
<h1 style={{ ...style, color: 'red' }} {...props} />
);
const slatePlugin = slate([
new slatePlugins.HeadingsPlugin({
allowedLevels: [1],
getComponent: ({ type }: { type: string; data: any }) => RedH1,
})
```

## Image

Expand Down
55 changes: 40 additions & 15 deletions examples/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import '@react-page/ui/lib/index.css';

// The rich text area plugin
import slate from '@react-page/plugins-slate';

import '@react-page/plugins-slate/lib/index.css';

// The spacer plugin
Expand Down Expand Up @@ -75,36 +76,55 @@ import './styles.css';
import { ImageUploadType } from '@react-page/ui/lib/ImageUpload';
import { Plugins } from '@react-page/core/lib/service/plugin/classes';

const fakeImageUploadService: (url: string) => ImageUploadType = (defaultUrl) => (file, reportProgress) => {
const fakeImageUploadService: (url: string) => ImageUploadType = defaultUrl => (
file,
reportProgress
) => {
return new Promise((resolve, reject) => {
let counter = 0;
const interval = setInterval(() => {
counter++;
reportProgress(counter * 10);
if (counter > 9) {
clearInterval(interval);
alert('This is a fake image upload service, please provide actual implementation via plugin properties');
alert(
'This is a fake image upload service, please provide actual implementation via plugin properties'
);
resolve({ url: defaultUrl });
}
}, 500);
});
};

if (process.env.NODE_ENV !== 'production' && process.env.REACT_APP_TRACE_UPDATES) {
if (
process.env.NODE_ENV !== 'production' &&
process.env.REACT_APP_TRACE_UPDATES
) {
const { whyDidYouUpdate } = require('why-did-you-update');
whyDidYouUpdate(React);
}

const slatePlugin = slate();
// Define which plugins we want to use (all of the above)
const plugins: Plugins = {
content: [slate(), spacer, imagePlugin({ imageUpload: fakeImageUploadService('/images/react.png') }), video, divider, html5video],
content: [
slatePlugin,
spacer,
imagePlugin({ imageUpload: fakeImageUploadService('/images/react.png') }),
video,
divider,
html5video,
],
layout: [
background({
defaultPlugin: slate(),
defaultPlugin: slatePlugin,
imageUpload: fakeImageUploadService('/images/sea-bg.jpg'),
enabledModes: ModeEnum.COLOR_MODE_FLAG | ModeEnum.IMAGE_MODE_FLAG | ModeEnum.GRADIENT_MODE_FLAG,
enabledModes:
ModeEnum.COLOR_MODE_FLAG |
ModeEnum.IMAGE_MODE_FLAG |
ModeEnum.GRADIENT_MODE_FLAG,
}),
parallax({ defaultPlugin: slate() }),
parallax({ defaultPlugin: slatePlugin }),
],

// If you pass the native key the editor will be able to handle native drag and drop events (such as links, text, etc).
Expand All @@ -127,7 +147,7 @@ editor.trigger.mode.edit();
// Render the editables - the areas that are editable
const elements = document.querySelectorAll<HTMLDivElement>('.editable');
elements.forEach(element => {
ReactDOM.render((
ReactDOM.render(
<Editable
editor={editor}
id={element.dataset.id as string}
Expand All @@ -136,20 +156,25 @@ elements.forEach(element => {
console.log(state)
}
}}*/
/>
), element);
/>,
element
);
});

// Render the ui controls, you could implement your own here, of course.
ReactDOM.render((
ReactDOM.render(
<div>
<Trash editor={editor} />
<DisplayModeToggle editor={editor} />
<Toolbar editor={editor} />
</div>
), document.getElementById('controls'));
</div>,
document.getElementById('controls')
);

// Render as beautified mark up (html)
ReactDOM.render(<HTMLRenderer state={content[0]} plugins={plugins} />, document.getElementById('editable-static'));
ReactDOM.render(
<HTMLRenderer state={content[0]} plugins={plugins} />,
document.getElementById('editable-static')
);

editor.trigger.editable.add({ id: '10', cells: [] });
editor.trigger.editable.add({ id: '10', cells: [] });
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"@types/jest": "^23.3.10",
"@types/react": "16.7.17",
"@types/react-dom": "16.0.11",
"@types/slate": "0.43.6",
"@types/slate-react": "0.20.3",
"@types/slate": "^0.44.9",
"@types/slate-react": "^0.21.0",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.0.0",
"babel-jest": "^23.6.0",
Expand Down
9 changes: 5 additions & 4 deletions packages/plugins/content/slate/src/Component/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ import { Editor, getEventTransfer } from 'slate-react';
import { BottomToolbar, ThemeProvider } from '@react-page/ui';
import debounce from 'lodash.debounce';

import { html as serializer } from '../hooks';
import { SlateProps } from './../types/component';
import { Value, Editor as CoreEditor } from 'slate';
import { Cancelable } from 'lodash';
import { SlateProps } from 'src/types/component';

const theme = createMuiTheme({
palette: {
Expand Down Expand Up @@ -124,8 +123,10 @@ class Slate extends React.Component<SlateProps, SlateState> {
return next();
}

// tslint:disable-next-line:no-any
const { document } = serializer.deserialize((transfer as any).html);
const { document } = this.props.serializeFunctions.htmlToSlate(
// tslint:disable-next-line:no-any
(transfer as any).html
);

return editor.insertFragment(document);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,13 @@
*/

import * as React from 'react';
import { RenderNodeProps } from 'slate-react';
import { Block } from 'slate';
import { Editor } from 'slate-react';

const Link: React.SFC<RenderNodeProps> = ({ attributes, children, node }) => {
// tslint:disable-next-line:no-any
const { data } = node as any as Block;
const href = data.get('href');

return (
<a {...attributes} href={href}>
{children}
</a>
);
};

export default Link;
export default ({ plugins, state }) => (
<Editor
readOnly={true}
className="ory-plugins-content-slate-container"
value={state.editorState}
plugins={plugins}
/>
);
41 changes: 30 additions & 11 deletions packages/plugins/content/slate/src/__tests__/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ import map from 'ramda/src/map';
import * as unexpected from 'unexpected';
import { SlateState } from '../types/state';

import serialization from '../serialization';
import defaultPlugins from '../plugins/defaultPlugins';

const serializationFunctions = serialization({ plugins: defaultPlugins });

const expect = unexpected.clone();

describe('hooks', () => {
describe('merge', () => {
it('does nothing if only one state is passed', () => {
const expected = hooks.unserialize({
const expected = serializationFunctions.unserialize({
importFromHtml:
'<h1>European? British? These ‘Brexit’ Voters Identify as English</h1>',
});
const subject = hooks.merge([expected]);

expect(hooks.serialize(subject), 'to equal', hooks.serialize(expected));
expect(
serializationFunctions.serialize(subject),
'to equal',
serializationFunctions.serialize(expected)
);
});

it('merges the states if more than one state is passed', () => {
Expand All @@ -48,27 +57,35 @@ describe('hooks', () => {
];

const subject = hooks.merge(
map(importFromHtml => hooks.unserialize({ importFromHtml }), html)
map(
importFromHtml =>
serializationFunctions.unserialize({ importFromHtml }),
html
)
);

const expected = hooks.unserialize({
const expected = serializationFunctions.unserialize({
importFromHtml: html.join(''),
});

expect(hooks.serialize(subject), 'to equal', hooks.serialize(expected));
expect(
serializationFunctions.serialize(subject),
'to equal',
serializationFunctions.serialize(expected)
);
});
});

describe('split', () => {
it('does nothing if the state contains only one block element', () => {
const expected = hooks.unserialize({
const expected = serializationFunctions.unserialize({
importFromHtml:
'<h1>European? British? These ‘Brexit’ Voters Identify as English</h1>',
});
const subject = hooks.split(expected);

expect(map(hooks.serialize, subject), 'to equal', [
hooks.serialize(expected),
expect(map(serializationFunctions.serialize, subject), 'to equal', [
serializationFunctions.serialize(expected),
]);
});

Expand All @@ -78,18 +95,20 @@ describe('hooks', () => {
'<p>Lorem ipsum dolor sit</p>',
];

const editorState = hooks.unserialize({ importFromHtml: html.join('') });
const editorState = serializationFunctions.unserialize({
importFromHtml: html.join(''),
});

const splitStates: SlateState[] = hooks.split(editorState);

expect(
hooks.html.serialize(splitStates[0].editorState),
serializationFunctions.slateToHtml(splitStates[0].editorState),
'to equal',
html[0]
);

expect(
hooks.html.serialize(splitStates[1].editorState),
serializationFunctions.slateToHtml(splitStates[1].editorState),
'to equal',
html[1]
);
Expand Down
16 changes: 12 additions & 4 deletions packages/plugins/content/slate/src/__tests__/serialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
*
*/
import { Value } from 'slate';
import { html } from '../hooks';

import Plain from 'slate-plain-serializer';
import serialization from '../serialization';
import defaultPlugins from '../plugins/defaultPlugins';

const serializationFunctions = serialization({ plugins: defaultPlugins });

describe('serialize to html', () => {
[
Expand Down Expand Up @@ -243,14 +247,18 @@ describe('serialize to html', () => {
].forEach((c, k) => {
describe(`test case ${k}`, () => {
it('should serialize properly', () => {
// tslint:disable-next-line:no-any
expect(html.serialize(Value.fromJSON(c.i as any))).toEqual(c.o);
expect(
// tslint:disable-next-line:no-any
serializationFunctions.slateToHtml(Value.fromJSON(c.i as any))
).toEqual(c.o);
});
it(`should deserialize properly: ${c.o}`, () => {
if (c.skip) {
return;
}
expect(Plain.serialize(html.deserialize(c.o))).toEqual(
expect(
Plain.serialize(serializationFunctions.htmlToSlate(c.o))
).toEqual(
// tslint:disable-next-line:no-any
Plain.serialize(Value.fromJSON(c.i as any))
);
Expand Down
Loading

0 comments on commit 89eb22b

Please sign in to comment.