diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c3321c11a..1515aaf8f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -23,6 +23,7 @@ Changelog * Added error handling to the Draftail editor (Thibaud Colas) * Add new `wagtail_icon` template tag to facilitate making admin icons accessible (Sander Tuit) * Set `ALLOWED_HOSTS` in the project template to allow any host in development (Tom Dyson) + * Expose reusable client-side code to build Draftail extensions (Thibaud Colas) * Fix: Status button on 'edit page' now links to the correct URL when live and draft slug differ (LB (Ben Johnston)) * Fix: Image title text in the gallery and in the chooser now wraps for long filenames (LB (Ben Johnston), Luiz Boaretto) * Fix: Move image editor action buttons to the bottom of the form on mobile (Julian Gallo) diff --git a/client/src/components/Draftail/index.js b/client/src/components/Draftail/index.js index ed8e5a17f..f68595d16 100644 --- a/client/src/components/Draftail/index.js +++ b/client/src/components/Draftail/index.js @@ -11,8 +11,9 @@ export { default as Document } from './decorators/Document'; export { default as ImageBlock } from './blocks/ImageBlock'; export { default as EmbedBlock } from './blocks/EmbedBlock'; -export { default as ModalWorkflowSource } from './sources/ModalWorkflowSource'; - +import ModalWorkflowSource from './sources/ModalWorkflowSource'; +import Tooltip from './Tooltip/Tooltip'; +import TooltipEntity from './decorators/TooltipEntity'; import EditorFallback from './EditorFallback/EditorFallback'; // 1024x1024 SVG path rendering of the "↵" character, that renders badly in MS Edge. @@ -122,4 +123,8 @@ const initEditor = (selector, options, currentScript) => { export default { initEditor, registerPlugin, + // Components exposed for third-party reuse. + ModalWorkflowSource, + Tooltip, + TooltipEntity, }; diff --git a/client/src/components/Draftail/index.test.js b/client/src/components/Draftail/index.test.js index cf3ce15e8..a56565453 100644 --- a/client/src/components/Draftail/index.test.js +++ b/client/src/components/Draftail/index.test.js @@ -1,6 +1,5 @@ import draftail, { wrapWagtailIcon, - ModalWorkflowSource, Link, Document, ImageBlock, @@ -133,9 +132,12 @@ describe('Draftail', () => { }); }); - it('#ModalWorkflowSource', () => expect(ModalWorkflowSource).toBeDefined()); it('#Link', () => expect(Link).toBeDefined()); it('#Document', () => expect(Document).toBeDefined()); it('#ImageBlock', () => expect(ImageBlock).toBeDefined()); it('#EmbedBlock', () => expect(EmbedBlock).toBeDefined()); + + it('#ModalWorkflowSource', () => expect(draftail.ModalWorkflowSource).toBeDefined()); + it('#Tooltip', () => expect(draftail.Tooltip).toBeDefined()); + it('#TooltipEntity', () => expect(draftail.TooltipEntity).toBeDefined()); }); diff --git a/client/src/index.js b/client/src/index.js index 9cc7b0282..997c45c78 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -7,6 +7,7 @@ import Button from './components/Button/Button'; import Icon from './components/Icon/Icon'; import PublicationStatus from './components/PublicationStatus/PublicationStatus'; import LoadingSpinner from './components/LoadingSpinner/LoadingSpinner'; +import Portal from './components/Portal/Portal'; import Transition from './components/Transition/Transition'; import Explorer, { ExplorerToggle, @@ -18,6 +19,7 @@ export { Icon, PublicationStatus, LoadingSpinner, + Portal, Transition, Explorer, ExplorerToggle, diff --git a/client/src/index.test.js b/client/src/index.test.js index e7a5b4e2e..aca9caf2c 100644 --- a/client/src/index.test.js +++ b/client/src/index.test.js @@ -3,6 +3,7 @@ import { Icon, PublicationStatus, LoadingSpinner, + Portal, Transition, Explorer, ExplorerToggle, @@ -26,6 +27,10 @@ describe('wagtail package API', () => { expect(LoadingSpinner).toBeDefined(); }); + it('has Portal', () => { + expect(Portal).toBeDefined(); + }); + it('has Transition', () => { expect(Transition).toBeDefined(); }); diff --git a/client/tests/stubs.js b/client/tests/stubs.js index 3506d35b5..222d9b21a 100644 --- a/client/tests/stubs.js +++ b/client/tests/stubs.js @@ -46,6 +46,8 @@ global.wagtailConfig = { global.wagtailVersion = '1.6a1'; +global.wagtail = {}; + global.chooserUrls = { documentChooser: '/admin/documents/chooser/', emailLinkChooser: '/admin/choose-email-link/', diff --git a/docs/advanced_topics/customisation/admin_templates.rst b/docs/advanced_topics/customisation/admin_templates.rst index d2f4d70c8..93684afa9 100644 --- a/docs/advanced_topics/customisation/admin_templates.rst +++ b/docs/advanced_topics/customisation/admin_templates.rst @@ -188,5 +188,25 @@ To make this easier, Wagtail exposes its React-related dependencies as global va window.ReactDOM; // 'react-transition-group/CSSTransitionGroup' window.CSSTransitionGroup; + +Wagtail also exposes some of its own React components. You can reuse: + +.. code-block:: javascript + + window.wagtail.components.Icon; + window.wagtail.components.Portal; + +Pages containing rich text editors also have access to: + +.. code-block:: javascript + // 'draft-js' window.DraftJS; + // 'draftail' + window.Draftail; + + // Wagtail’s Draftail-related APIs and components. + window.draftail; + window.draftail.ModalWorkflowSource; + window.draftail.Tooltip; + window.draftail.TooltipEntity; diff --git a/docs/advanced_topics/customisation/extending_draftail.rst b/docs/advanced_topics/customisation/extending_draftail.rst index 2f7dcaa49..667ec3525 100644 --- a/docs/advanced_topics/customisation/extending_draftail.rst +++ b/docs/advanced_topics/customisation/extending_draftail.rst @@ -141,7 +141,7 @@ Here are the main requirements to create a new entity feature: * Like for inline styles and blocks, set up the to/from DB conversion. * The conversion usually is more involved, since entities contain data that needs to be serialised to HTML. -To write the React components, Wagtail exposes its own React and Draft.js dependencies as global variables. Read more about this in :ref:`extending_clientside_components`. +To write the React components, Wagtail exposes its own React, Draft.js and Draftail dependencies as global variables. Read more about this in :ref:`extending_clientside_components`. To go further, please look at the `Draftail documentation `_ as well as the `Draft.js exporter documentation `_. Here is a detailed example to showcase how those tools are used in the context of Wagtail. diff --git a/docs/releases/2.1.rst b/docs/releases/2.1.rst index 40151bf8e..159cffe01 100644 --- a/docs/releases/2.1.rst +++ b/docs/releases/2.1.rst @@ -42,6 +42,7 @@ Other features * Added error handling to the Draftail editor (Thibaud Colas) * Add new `wagtail_icon` template tag to facilitate making admin icons accessible (Sander Tuit) * Set `ALLOWED_HOSTS` in the project template to allow any host in development (Tom Dyson) + * Expose reusable client-side code to build Draftail extensions (Thibaud Colas) Bug fixes ~~~~~~~~~ diff --git a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js index 1664ba6f1..b5dd352eb 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js +++ b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.js @@ -1,5 +1,5 @@ +import * as Draftail from 'draftail'; import draftail, { - ModalWorkflowSource, Link, Document, ImageBlock, @@ -10,6 +10,8 @@ import draftail, { * Entry point loaded when the Draftail editor is in use. */ + // Expose Draftail package as a global. +window.Draftail = Draftail; // Expose module as a global. window.draftail = draftail; @@ -17,22 +19,22 @@ window.draftail = draftail; const plugins = [ { type: 'DOCUMENT', - source: ModalWorkflowSource, + source: draftail.ModalWorkflowSource, decorator: Document, }, { type: 'LINK', - source: ModalWorkflowSource, + source: draftail.ModalWorkflowSource, decorator: Link, }, { type: 'IMAGE', - source: ModalWorkflowSource, + source: draftail.ModalWorkflowSource, block: ImageBlock, }, { type: 'EMBED', - source: ModalWorkflowSource, + source: draftail.ModalWorkflowSource, block: EmbedBlock, }, ]; 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 93188906c..00ed2bcdc 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js +++ b/wagtail/admin/static_src/wagtailadmin/app/draftail.entry.test.js @@ -1,10 +1,14 @@ require('./draftail.entry'); describe('draftail.entry', () => { - it('exposes global', () => { + it('exposes module as global', () => { expect(window.draftail).toBeDefined(); }); + it('exposes package as global', () => { + expect(window.Draftail).toBeDefined(); + }); + it('has defaults registered', () => { expect(Object.keys(window.draftail.registerPlugin({}))).toEqual(["DOCUMENT", "LINK", "IMAGE", "EMBED", "undefined"]); }); diff --git a/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.js b/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.js index e0c90612f..a0faf566f 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.js +++ b/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.js @@ -1,7 +1,13 @@ -import { initExplorer } from 'wagtail-client'; +import { initExplorer, Icon, Portal } from 'wagtail-client'; + +// Expose components as globals for third-party reuse. +window.wagtail.components = { + Icon, + Portal, +}; /** - * Admin JS entry point. Add in here code to run once the page is loaded. + * Add in here code to run once the page is loaded. */ document.addEventListener('DOMContentLoaded', () => { const explorerNode = document.querySelector('[data-explorer-menu]'); diff --git a/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.test.js b/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.test.js index cb4a83a1e..9e0461073 100644 --- a/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.test.js +++ b/wagtail/admin/static_src/wagtailadmin/app/wagtailadmin.entry.test.js @@ -8,6 +8,10 @@ require('./wagtailadmin.entry'); describe('wagtailadmin.entry', () => { const [event, listener] = document.addEventListener.mock.calls[0]; + it('exposes components for reuse', () => { + expect(Object.keys(window.wagtail.components)).toEqual(['Icon', 'Portal']); + }); + it('DOMContentLoaded', () => { expect(event).toBe('DOMContentLoaded'); });