Refactor Draftail JS plugin API to be more similar to the rich text features registry API

This commit is contained in:
Thibaud Colas 2018-02-08 00:01:32 +02:00 committed by Matt Westcott
parent 1f395059be
commit 7675084cbe
12 changed files with 73 additions and 107 deletions

View file

@ -16,9 +16,8 @@ Object {
"entityTypes": Array [
Object {
"block": [Function],
"decorator": undefined,
"source": [Function],
"type": "Image",
"type": "IMAGE",
},
],
"inlineStyles": Array [],

View file

@ -46,13 +46,12 @@ export const initEditor = (fieldName, options) => {
const inlineStyles = options.inlineStyles || [];
let entityTypes = options.entityTypes || [];
entityTypes = entityTypes.map(wrapWagtailIcon).map(type =>
Object.assign(type, {
source: registry.getSource(type.source),
decorator: registry.getDecorator(type.decorator),
block: registry.getBlock(type.block),
})
);
entityTypes = entityTypes.map(wrapWagtailIcon).map((type) => {
const plugin = registry.getPlugin(type.type);
// Override the properties defined in the JS plugin: Python should be the source of truth.
return Object.assign({}, plugin, type);
});
const enableHorizontalRule = options.enableHorizontalRule ? {
description: STRINGS.HORIZONTAL_LINE,

View file

@ -45,17 +45,15 @@ describe('Draftail', () => {
field.value = 'null';
document.body.appendChild(field);
registry.registerSources({
ModalWorkflowSource: () => {},
});
registry.registerBlocks({
ImageBlock: () => {},
registry.registerPlugin({
type: 'IMAGE',
source: () => {},
block: () => {},
});
initEditor('test', {
entityTypes: [
{ type: 'Image', source: 'ModalWorkflowSource', block: 'ImageBlock' },
{ type: 'IMAGE' },
],
enableHorizontalRule: true,
});

View file

@ -1,22 +1,14 @@
const registry = {
decorators: {},
blocks: {},
sources: {},
const plugins = {};
const registerPlugin = (plugin) => {
plugins[plugin.type] = plugin;
return plugins;
};
const registerDecorators = (decorators) => Object.assign(registry.decorators, decorators);
const registerBlocks = (blocks) => Object.assign(registry.blocks, blocks);
const registerSources = (sources) => Object.assign(registry.sources, sources);
const getDecorator = name => registry.decorators[name];
const getBlock = name => registry.blocks[name];
const getSource = name => registry.sources[name];
const getPlugin = (type) => plugins[type];
export default {
registerDecorators,
registerBlocks,
registerSources,
getDecorator,
getBlock,
getSource,
registerPlugin,
getPlugin,
};

View file

@ -1,39 +1,15 @@
import registry from './registry';
describe('registry', () => {
describe('sources', () => {
it('works', () => {
expect(registry.getSource('UndefinedSource')).not.toBeDefined();
it('works', () => {
const plugin = {
type: 'TEST',
source: null,
decorator: null,
};
registry.registerSources({
TestSource: null,
});
expect(registry.getSource('TestSource')).toBe(null);
});
});
describe('decorators', () => {
it('works', () => {
expect(registry.getDecorator('UndefinedDecorator')).not.toBeDefined();
registry.registerDecorators({
TestDecorator: null,
});
expect(registry.getDecorator('TestDecorator')).toBe(null);
});
});
describe('blocks', () => {
it('works', () => {
expect(registry.getBlock('UndefinedBlock')).not.toBeDefined();
registry.registerBlocks({
TestBlock: null,
});
expect(registry.getBlock('TestBlock')).toBe(null);
});
expect(registry.getPlugin('TEST')).not.toBeDefined();
registry.registerPlugin(plugin);
expect(registry.getPlugin('TEST')).toBe(plugin);
});
});

View file

@ -175,8 +175,6 @@ In order to achieve this, we start with registering the rich text feature like f
'type': type_,
'label': '$',
'description': 'Stock',
'source': 'StockSource',
'decorator': 'Stock'
}
features.register_editor_plugin(
@ -282,14 +280,8 @@ We define the source component:
}
}
window.draftail.registerSources({
StockSource: StockSource,
});
This source component uses data and callbacks provided by `Draftail <https://github.com/springload/draftail>`_.
It also uses dependencies from global variables see :ref:`extending_clientside_components`.
Note how after the source declaration it is then registered for Draftail to know about it.
We then create the decorator component:
@ -307,12 +299,17 @@ We then create the decorator component:
}, props.children);
};
window.draftail.registerDecorators({
Stock: Stock,
});
This is a straightforward React component. It does not use JSX since we do not want to have to use a build step for our JavaScript. It uses ES6 syntax this would not work in IE11 unless it was converted back to ES5 with a build step.
This is a straightforward React component. It does not use JSX since we do not want to have to use a build step for our JavaScript. It uses ES6 syntax this would not work in IE11 unless it was converted back to ES5.
We also have to register this with Draftail.
Finally, we register the JS components of our plugin:
.. code-block:: javascript
window.draftail.registerPlugin({
type: 'STOCK',
source: StockSource,
decorator: Stock,
});
And thats it! All of this setup will finally produce the following HTML on the sites front-end:

View file

@ -9,22 +9,36 @@ import {
} from '../../../../../client/src/components/Draftail/index';
/**
* Expose as a global, and register the built-in entities.
* Entry point loaded when the Draftail editor is in use.
*/
const draftail = registry;
window.draftail = registry;
window.draftail.initEditor = initEditor;
// Expose Draftail as a global, with the initEditor function.
draftail.initEditor = initEditor;
window.draftail = draftail;
window.draftail.registerSources({
ModalWorkflowSource,
});
// Plugins for the built-in entities.
const plugins = [
{
type: 'DOCUMENT',
source: ModalWorkflowSource,
decorator: Document,
},
{
type: 'LINK',
source: ModalWorkflowSource,
decorator: Link,
},
{
type: 'IMAGE',
source: ModalWorkflowSource,
block: ImageBlock,
},
{
type: 'EMBED',
source: ModalWorkflowSource,
block: EmbedBlock,
},
];
window.draftail.registerDecorators({
Link,
Document,
});
window.draftail.registerBlocks({
ImageBlock,
EmbedBlock,
});
plugins.forEach(draftail.registerPlugin);

View file

@ -6,10 +6,9 @@ describe('draftail.entry', () => {
});
it('has defaults registered', () => {
expect(window.draftail.getSource('ModalWorkflowSource')).toBeDefined();
expect(window.draftail.getDecorator('Link')).toBeDefined();
expect(window.draftail.getDecorator('Document')).toBeDefined();
expect(window.draftail.getBlock('ImageBlock')).toBeDefined();
expect(window.draftail.getBlock('EmbedBlock')).toBeDefined();
expect(window.draftail.getPlugin('LINK')).toBeDefined();
expect(window.draftail.getPlugin('DOCUMENT')).toBeDefined();
expect(window.draftail.getPlugin('IMAGE')).toBeDefined();
expect(window.draftail.getPlugin('EMBED')).toBeDefined();
});
});

View file

@ -430,8 +430,6 @@ def register_core_features(features):
'type': 'LINK',
'icon': 'link',
'description': ugettext('Link'),
'source': 'ModalWorkflowSource',
'decorator': 'Link',
# We want to enforce constraints on which links can be pasted into rich text.
# Keep only the attributes Wagtail needs.
'attributes': ['url', 'id', 'parentId'],

View file

@ -90,8 +90,6 @@ def register_document_feature(features):
'type': 'DOCUMENT',
'icon': 'doc-full',
'description': ugettext('Document'),
'source': 'ModalWorkflowSource',
'decorator': 'Document',
})
)

View file

@ -54,8 +54,6 @@ def register_embed_feature(features):
'type': 'EMBED',
'icon': 'media',
'description': _('Embed'),
'source': 'ModalWorkflowSource',
'block': 'EmbedBlock',
})
)

View file

@ -89,8 +89,6 @@ def register_image_feature(features):
'type': 'IMAGE',
'icon': 'image',
'description': ugettext('Image'),
'source': 'ModalWorkflowSource',
'block': 'ImageBlock',
# We do not want users to be able to copy-paste hotlinked images into rich text.
# Keep only the attributes Wagtail needs.
'attributes': ['id', 'src', 'alt', 'format'],