Browse Source

Add Draftail error handling component

Thibaud Colas 7 years ago
parent
commit
7740d2d615

+ 1 - 0
client/src/components/Draftail/Draftail.scss

@@ -17,6 +17,7 @@ $draftail-editor-font-family: $font-serif;
 @import '../../../../node_modules/draftail/lib/index';
 
 @import './Tooltip/Tooltip';
+@import './EditorFallback/EditorFallback';
 
 @import './decorators/TooltipEntity';
 

+ 86 - 0
client/src/components/Draftail/EditorFallback/EditorFallback.js

@@ -0,0 +1,86 @@
+import PropTypes from 'prop-types';
+import React, { PureComponent } from 'react';
+import { convertFromRaw } from 'draft-js';
+
+import { STRINGS } from '../../../config/wagtailConfig';
+
+class EditorFallback extends PureComponent {
+  constructor(props) {
+    super(props);
+
+    const { field } = props;
+
+    this.state = {
+      error: null,
+      isContentShown: false,
+      initialContent: field.value,
+    };
+
+    this.renderError = this.renderError.bind(this);
+    this.toggleContent = this.toggleContent.bind(this);
+  }
+
+  componentDidCatch(error) {
+    const { field } = this.props;
+    const { initialContent } = this.state;
+
+    this.setState({ error });
+
+    field.value = initialContent;
+  }
+
+  toggleContent() {
+    const { isContentShown } = this.state;
+    this.setState({ isContentShown: !isContentShown });
+  }
+
+  renderError() {
+    const { field } = this.props;
+    const { isContentShown } = this.state;
+    const content = field.rawContentState && convertFromRaw(field.rawContentState).getPlainText();
+
+    return (
+      <div className="Draftail-Editor">
+        <div className="Draftail-Toolbar">
+          <button
+            type="button"
+            className="Draftail-ToolbarButton"
+            onClick={() => window.location.reload(false)}
+          >
+            {STRINGS.RELOAD_PAGE}
+          </button>
+          {content && (
+            <button
+              type="button"
+              className="Draftail-ToolbarButton"
+              onClick={this.toggleContent}
+            >
+              {STRINGS.SHOW_LATEST_CONTENT}
+            </button>
+          )}
+        </div>
+        <div className="DraftEditor-root">
+          <div className="public-DraftEditorPlaceholder-inner">
+            {STRINGS.EDITOR_CRASH}
+          </div>
+        </div>
+        {isContentShown && (
+          <textarea className="EditorFallback__textarea" value={content} readOnly />
+        )}
+      </div>
+    );
+  }
+
+  render() {
+    const { children } = this.props;
+    const { error } = this.state;
+
+    return error ? this.renderError() : children;
+  }
+}
+
+EditorFallback.propTypes = {
+  children: PropTypes.node.isRequired,
+};
+
+export default EditorFallback;

+ 4 - 0
client/src/components/Draftail/EditorFallback/EditorFallback.scss

@@ -0,0 +1,4 @@
+.EditorFallback__textarea {
+    resize: vertical;
+    min-height: 150px;
+}

+ 33 - 24
client/src/components/Draftail/index.js

@@ -13,6 +13,8 @@ export { default as EmbedBlock } from './blocks/EmbedBlock';
 
 export { default as ModalWorkflowSource } from './sources/ModalWorkflowSource';
 
+import EditorFallback from './EditorFallback/EditorFallback';
+
 // 1024x1024 SVG path rendering of the "↵" character, that renders badly in MS Edge.
 const BR_ICON = 'M.436 633.471l296.897-296.898v241.823h616.586V94.117h109.517v593.796H297.333v242.456z';
 
@@ -61,6 +63,7 @@ const initEditor = (selector, options, currentScript) => {
   field.parentNode.appendChild(editorWrapper);
 
   const serialiseInputValue = rawContentState => {
+    field.rawContentState = rawContentState;
     field.value = JSON.stringify(rawContentState);
   };
 
@@ -80,34 +83,40 @@ const initEditor = (selector, options, currentScript) => {
   } : false;
 
   const rawContentState = JSON.parse(field.value);
+  field.rawContentState = rawContentState;
+
+  const editorRef = (ref) => {
+    // Bind editor instance to its field so it can be accessed imperatively elsewhere.
+    field.draftailEditor = ref;
+  };
 
   const editor = (
-    <DraftailEditor
-      rawContentState={rawContentState}
-      onSave={serialiseInputValue}
-      placeholder={STRINGS.WRITE_HERE}
-      spellCheck={true}
-      enableLineBreak={{
-        description: STRINGS.LINE_BREAK,
-        icon: BR_ICON,
-      }}
-      showUndoControl={{ description: STRINGS.UNDO }}
-      showRedoControl={{ description: STRINGS.REDO }}
-      maxListNesting={4}
-      // Draft.js + IE 11 presents some issues with pasting rich text. Disable rich paste there.
-      stripPastedStyles={IS_IE11}
-      {...options}
-      blockTypes={blockTypes.map(wrapWagtailIcon)}
-      inlineStyles={inlineStyles.map(wrapWagtailIcon)}
-      entityTypes={entityTypes}
-      enableHorizontalRule={enableHorizontalRule}
-    />
+    <EditorFallback field={field}>
+      <DraftailEditor
+        ref={editorRef}
+        rawContentState={rawContentState}
+        onSave={serialiseInputValue}
+        placeholder={STRINGS.WRITE_HERE}
+        spellCheck={true}
+        enableLineBreak={{
+          description: STRINGS.LINE_BREAK,
+          icon: BR_ICON,
+        }}
+        showUndoControl={{ description: STRINGS.UNDO }}
+        showRedoControl={{ description: STRINGS.REDO }}
+        maxListNesting={4}
+        // Draft.js + IE 11 presents some issues with pasting rich text. Disable rich paste there.
+        stripPastedStyles={IS_IE11}
+        {...options}
+        blockTypes={blockTypes.map(wrapWagtailIcon)}
+        inlineStyles={inlineStyles.map(wrapWagtailIcon)}
+        entityTypes={entityTypes}
+        enableHorizontalRule={enableHorizontalRule}
+      />
+    </EditorFallback>
   );
 
-  const draftailEditor = ReactDOM.render(editor, editorWrapper);
-
-  // Bind editor instance to its field so it can be accessed imperatively elsewhere.
-  field.draftailEditor = draftailEditor;
+  ReactDOM.render(editor, editorWrapper);
 };
 
 export default {

+ 3 - 0
client/tests/stubs.js

@@ -36,6 +36,9 @@ global.wagtailConfig = {
     LINE_BREAK: 'Line break',
     UNDO: 'Undo',
     REDO: 'Redo',
+    RELOAD_PAGE: 'Reload the page',
+    SHOW_LATEST_CONTENT: 'Show latest content',
+    EDITOR_CRASH: 'The editor just crashed. Content has been reset to the last saved version.',
   },
 };
 

+ 3 - 0
wagtail/admin/templates/wagtailadmin/admin_base.html

@@ -44,6 +44,9 @@
                 LINE_BREAK: "{% trans 'Line break' %}",
                 UNDO: "{% trans 'Undo' %}",
                 REDO: "{% trans 'Redo' %}",
+                RELOAD_PAGE: "{% trans 'Reload the page' %}",
+                SHOW_LATEST_CONTENT: "{% trans 'Show latest content' %}",
+                EDITOR_CRASH: "{% trans 'The editor just crashed. Content has been reset to the last saved version.' %}",
             };
 
             wagtailConfig.ADMIN_URLS = {