diff --git a/client/src/components/Draftail/__snapshots__/index.test.js.snap b/client/src/components/Draftail/__snapshots__/index.test.js.snap index e2046662f..e72ea0194 100644 --- a/client/src/components/Draftail/__snapshots__/index.test.js.snap +++ b/client/src/components/Draftail/__snapshots__/index.test.js.snap @@ -16,9 +16,8 @@ Object { "entityTypes": Array [ Object { "block": [Function], - "decorator": undefined, "source": [Function], - "type": "Image", + "type": "IMAGE", }, ], "inlineStyles": Array [], diff --git a/client/src/components/Draftail/index.js b/client/src/components/Draftail/index.js index a9787a485..b0b7071ba 100644 --- a/client/src/components/Draftail/index.js +++ b/client/src/components/Draftail/index.js @@ -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, diff --git a/client/src/components/Draftail/index.test.js b/client/src/components/Draftail/index.test.js index 97ca96441..09a339437 100644 --- a/client/src/components/Draftail/index.test.js +++ b/client/src/components/Draftail/index.test.js @@ -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, }); diff --git a/client/src/components/Draftail/registry.js b/client/src/components/Draftail/registry.js index 75c6577de..17503985f 100644 --- a/client/src/components/Draftail/registry.js +++ b/client/src/components/Draftail/registry.js @@ -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, }; diff --git a/client/src/components/Draftail/registry.test.js b/client/src/components/Draftail/registry.test.js index 983bf52b8..009a17863 100644 --- a/client/src/components/Draftail/registry.test.js +++ b/client/src/components/Draftail/registry.test.js @@ -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); }); }); diff --git a/docs/advanced_topics/customisation/extending_draftail.rst b/docs/advanced_topics/customisation/extending_draftail.rst index 36a7085ea..1d0fa300a 100644 --- a/docs/advanced_topics/customisation/extending_draftail.rst +++ b/docs/advanced_topics/customisation/extending_draftail.rst @@ -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 `_. 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 that’s it! All of this setup will finally produce the following HTML on the site’s front-end: diff --git a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js index c7924a593..4cacedf1b 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js +++ b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js @@ -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); diff --git a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js index c5c2197e6..48acdc73e 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js +++ b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js @@ -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(); }); }); diff --git a/wagtail/admin/wagtail_hooks.py b/wagtail/admin/wagtail_hooks.py index 5fc040dcd..47c9c4ada 100644 --- a/wagtail/admin/wagtail_hooks.py +++ b/wagtail/admin/wagtail_hooks.py @@ -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'], diff --git a/wagtail/documents/wagtail_hooks.py b/wagtail/documents/wagtail_hooks.py index 3b04738a2..ee27c3ca3 100644 --- a/wagtail/documents/wagtail_hooks.py +++ b/wagtail/documents/wagtail_hooks.py @@ -90,8 +90,6 @@ def register_document_feature(features): 'type': 'DOCUMENT', 'icon': 'doc-full', 'description': ugettext('Document'), - 'source': 'ModalWorkflowSource', - 'decorator': 'Document', }) ) diff --git a/wagtail/embeds/wagtail_hooks.py b/wagtail/embeds/wagtail_hooks.py index ab0c6f4b2..a233e0bea 100644 --- a/wagtail/embeds/wagtail_hooks.py +++ b/wagtail/embeds/wagtail_hooks.py @@ -54,8 +54,6 @@ def register_embed_feature(features): 'type': 'EMBED', 'icon': 'media', 'description': _('Embed'), - 'source': 'ModalWorkflowSource', - 'block': 'EmbedBlock', }) ) diff --git a/wagtail/images/wagtail_hooks.py b/wagtail/images/wagtail_hooks.py index 72aac7bdc..a1873e6a0 100644 --- a/wagtail/images/wagtail_hooks.py +++ b/wagtail/images/wagtail_hooks.py @@ -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'],