2
0
Эх сурвалжийг харах

Update Link and Document decorators to Draftail 0.10 API

Thibaud Colas 7 жил өмнө
parent
commit
73ed313a9f

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

@@ -37,6 +37,8 @@ $color-editor-chrome-accent: lighten($color-editor-chrome, 20%);
 
 @import './Tooltip/Tooltip';
 
+@import './decorators/TooltipEntity';
+
 @import './blocks/MediaBlock';
 @import './blocks/ImageBlock';
 @import './blocks/EmbedBlock';

+ 12 - 8
client/src/components/Draftail/decorators/Document.js

@@ -1,21 +1,25 @@
 import PropTypes from 'prop-types';
 import React from 'react';
-import { Icon } from 'draftail';
 
-const Document = ({ entityKey, contentState, children }) => {
-  const { title } = contentState.getEntity(entityKey).getData();
+import Icon from '../../Icon/Icon';
+
+import TooltipEntity from '../decorators/TooltipEntity';
+
+const Document = props => {
+  const { entityKey, contentState } = props;
+  const { url } = contentState.getEntity(entityKey).getData();
   return (
-    <span data-tooltip={entityKey} className="RichEditor-link" title={title}>
-      <Icon name="icon-doc-full" />
-      {children}
-    </span>
+    <TooltipEntity
+      {...props}
+      icon={<Icon name="doc-full" />}
+      label={url.replace(/(^\w+:|^)\/\//, '').split('/')[0]}
+    />
   );
 };
 
 Document.propTypes = {
   entityKey: PropTypes.string.isRequired,
   contentState: PropTypes.object.isRequired,
-  children: PropTypes.node.isRequired,
 };
 
 export default Document;

+ 0 - 25
client/src/components/Draftail/decorators/Document.test.js

@@ -1,25 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { convertFromHTML, ContentState } from 'draft-js';
-import Document from './Document';
-
-describe('Document', () => {
-  it('exists', () => {
-    expect(Document).toBeDefined();
-  });
-
-  it('renders', () => {
-    const contentBlocks = convertFromHTML('<h1>aaaaaaaaaa</h1>');
-    const contentState = ContentState.createFromBlockArray(contentBlocks);
-    const contentStateWithEntity = contentState.createEntity('DOCUMENT', 'MUTABLE', { title: 'Test title' });
-    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
-    expect(shallow((
-      <Document
-        entityKey={entityKey}
-        contentState={contentStateWithEntity}
-      >
-        <span>Test children</span>
-      </Document>
-    ))).toMatchSnapshot();
-  });
-});

+ 12 - 7
client/src/components/Draftail/decorators/Link.js

@@ -1,22 +1,27 @@
 import PropTypes from 'prop-types';
 import React from 'react';
-import { Icon } from 'draftail';
 
-const Link = ({ entityKey, contentState, children }) => {
+import Icon from '../../Icon/Icon';
+
+import TooltipEntity from '../decorators/TooltipEntity';
+
+const Link = props => {
+  const { entityKey, contentState } = props;
   const { url } = contentState.getEntity(entityKey).getData();
+  const icon = url.startsWith('mailto:') ? 'mail' : 'link';
 
   return (
-    <span data-tooltip={entityKey} className="RichEditor-link">
-      <Icon name={`icon-${url.indexOf('mailto:') !== -1 ? 'mail' : 'link'}`} />
-      {children}
-    </span>
+    <TooltipEntity
+      {...props}
+      icon={<Icon name={icon} />}
+      label={url.replace(/(^\w+:|^)\/\//, '').split('/')[0]}
+    />
   );
 };
 
 Link.propTypes = {
   entityKey: PropTypes.string.isRequired,
   contentState: PropTypes.object.isRequired,
-  children: PropTypes.node.isRequired,
 };
 
 export default Link;

+ 0 - 40
client/src/components/Draftail/decorators/Link.test.js

@@ -1,40 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { convertFromHTML, ContentState } from 'draft-js';
-import Link from './Link';
-
-describe('Link', () => {
-  it('exists', () => {
-    expect(Link).toBeDefined();
-  });
-
-  it('renders', () => {
-    const contentBlocks = convertFromHTML('<h1>aaaaaaaaaa</h1>');
-    const contentState = ContentState.createFromBlockArray(contentBlocks);
-    const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'http://example.com/' });
-    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
-    expect(shallow((
-      <Link
-        entityKey={entityKey}
-        contentState={contentStateWithEntity}
-      >
-        <span>Test children</span>
-      </Link>
-    ))).toMatchSnapshot();
-  });
-
-  it('renders email', () => {
-    const contentBlocks = convertFromHTML('<h1>aaaaaaaaaa</h1>');
-    const contentState = ContentState.createFromBlockArray(contentBlocks);
-    const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'mailto:test@example.com' });
-    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
-    expect(shallow((
-      <Link
-        entityKey={entityKey}
-        contentState={contentStateWithEntity}
-      >
-        <span>Test children</span>
-      </Link>
-    ))).toMatchSnapshot();
-  });
-});

+ 100 - 0
client/src/components/Draftail/decorators/TooltipEntity.js

@@ -0,0 +1,100 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { Icon } from 'draftail';
+
+import Tooltip from '../Tooltip/Tooltip';
+import Portal from '../../Portal/Portal';
+
+class TooltipEntity extends Component {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      showTooltipAt: null,
+    };
+
+    this.openTooltip = this.openTooltip.bind(this);
+    this.closeTooltip = this.closeTooltip.bind(this);
+  }
+
+  openTooltip(e) {
+    const trigger = e.target;
+    this.setState({ showTooltipAt: trigger.getBoundingClientRect() });
+  }
+
+  closeTooltip() {
+    this.setState({ showTooltipAt: null });
+  }
+
+  render() {
+    const {
+      entityKey,
+      contentState,
+      children,
+      onEdit,
+      onRemove,
+      icon,
+      label,
+    } = this.props;
+    const { showTooltipAt } = this.state;
+    const { url } = contentState.getEntity(entityKey).getData();
+
+    // Contrary to what JSX A11Y says, this should be a button but it shouldn't be focusable.
+    /* eslint-disable springload/jsx-a11y/interactive-supports-focus */
+    return (
+      <a role="button" onMouseUp={this.openTooltip} className="TooltipEntity">
+        <Icon icon={icon} className="TooltipEntity__icon" />
+        {children}
+        {showTooltipAt && (
+          <Portal
+            onClose={this.closeTooltip}
+            closeOnClick
+            closeOnType
+            closeOnResize
+          >
+            <Tooltip target={showTooltipAt} direction="top">
+              <a
+                href={url}
+                title={url}
+                target="_blank"
+                rel="noopener noreferrer"
+                className="Tooltip__link"
+              >
+                {label}
+              </a>
+
+              <button
+                className="Tooltip__button"
+                onClick={onEdit.bind(null, entityKey)}
+              >
+                Edit
+              </button>
+
+              <button
+                className="Tooltip__button"
+                onClick={onRemove.bind(null, entityKey)}
+              >
+                Remove
+              </button>
+            </Tooltip>
+          </Portal>
+        )}
+      </a>
+    );
+  }
+}
+
+TooltipEntity.propTypes = {
+  entityKey: PropTypes.string.isRequired,
+  contentState: PropTypes.object.isRequired,
+  children: PropTypes.node.isRequired,
+  onEdit: PropTypes.func.isRequired,
+  onRemove: PropTypes.func.isRequired,
+  icon: PropTypes.oneOfType([
+    PropTypes.string.isRequired,
+    PropTypes.object.isRequired,
+  ]).isRequired,
+  label: PropTypes.string.isRequired,
+};
+
+export default TooltipEntity;

+ 12 - 0
client/src/components/Draftail/decorators/TooltipEntity.scss

@@ -0,0 +1,12 @@
+.TooltipEntity {
+    background: $color-light-blue;
+    border-bottom: 1px dotted $color-teal;
+    cursor: pointer;
+
+    &__icon {
+        color: $color-teal;
+        margin-right: 0.2em;
+        width: 1em;
+        height: 1em;
+    }
+}

+ 0 - 18
client/src/components/Draftail/decorators/__snapshots__/Document.test.js.snap

@@ -1,18 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Document renders 1`] = `
-<span
-  className="RichEditor-link"
-  data-tooltip="1"
-  title="Test title"
->
-  <Icon
-    className=""
-    name="icon-doc-full"
-    title={null}
-  />
-  <span>
-    Test children
-  </span>
-</span>
-`;

+ 0 - 33
client/src/components/Draftail/decorators/__snapshots__/Link.test.js.snap

@@ -1,33 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Link renders 1`] = `
-<span
-  className="RichEditor-link"
-  data-tooltip="1"
->
-  <Icon
-    className=""
-    name="icon-link"
-    title={null}
-  />
-  <span>
-    Test children
-  </span>
-</span>
-`;
-
-exports[`Link renders email 1`] = `
-<span
-  className="RichEditor-link"
-  data-tooltip="2"
->
-  <Icon
-    className=""
-    name="icon-mail"
-    title={null}
-  />
-  <span>
-    Test children
-  </span>
-</span>
-`;

+ 0 - 10
client/src/components/Draftail/decorators/index.js

@@ -1,10 +0,0 @@
-import Link from './Link';
-import Document from './Document';
-
-/**
- * Mapping object from name to component.
- */
-export default {
-  Link,
-  Document,
-};

+ 0 - 7
client/src/components/Draftail/decorators/index.test.js

@@ -1,7 +0,0 @@
-import entities from './index';
-
-describe('entities', () => {
-  it('exists', () => {
-    expect(entities).toBeInstanceOf(Object);
-  });
-});

+ 9 - 6
client/src/components/Draftail/index.js

@@ -4,8 +4,9 @@ import DraftailEditor from 'draftail';
 
 import Icon from '../Icon/Icon';
 
-import decorators from './decorators';
 import sources from './sources';
+import Link from './decorators/Link';
+import Document from './decorators/Document';
 import ImageBlock from './blocks/ImageBlock';
 import EmbedBlock from './blocks/EmbedBlock';
 
@@ -19,7 +20,7 @@ const wrapWagtailIcon = type => {
   }
 
   return type;
-}
+};
 
 export const initEditor = (fieldName, options = {}) => {
   const field = document.querySelector(`[name="${fieldName}"]`);
@@ -50,7 +51,7 @@ export const initEditor = (fieldName, options = {}) => {
         strategy: registry.getStrategy(type.type) || null,
         decorator: registry.getDecorator(type.decorator),
         block: registry.getBlock(type.block),
-      }),
+      })
     );
   }
 
@@ -77,9 +78,11 @@ export const initEditor = (fieldName, options = {}) => {
   ReactDOM.render(editor, editorWrapper);
 };
 
-// Register default Decorators and Sources
-registry.registerDecorators(decorators);
 registry.registerSources(sources);
+registry.registerDecorators({
+  Link,
+  Document,
+});
 registry.registerBlocks({
   ImageBlock,
   EmbedBlock,
@@ -93,7 +96,7 @@ const draftail = Object.assign(
     // createClass: React.createClass,
     // createElement: React.createElement,
   },
-  registry,
+  registry
 );
 
 export default draftail;