diff --git a/client/src/components/Draftail/Draftail.scss b/client/src/components/Draftail/Draftail.scss
index d063eb135..958368dc9 100644
--- a/client/src/components/Draftail/Draftail.scss
+++ b/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';
diff --git a/client/src/components/Draftail/decorators/Document.js b/client/src/components/Draftail/decorators/Document.js
index a30b557fb..beb46c6cf 100644
--- a/client/src/components/Draftail/decorators/Document.js
+++ b/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 (
-
-
- {children}
-
+ }
+ label={url.replace(/(^\w+:|^)\/\//, '').split('/')[0]}
+ />
);
};
Document.propTypes = {
entityKey: PropTypes.string.isRequired,
contentState: PropTypes.object.isRequired,
- children: PropTypes.node.isRequired,
};
export default Document;
diff --git a/client/src/components/Draftail/decorators/Document.test.js b/client/src/components/Draftail/decorators/Document.test.js
deleted file mode 100644
index ebd22fffa..000000000
--- a/client/src/components/Draftail/decorators/Document.test.js
+++ /dev/null
@@ -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('
aaaaaaaaaa
');
- const contentState = ContentState.createFromBlockArray(contentBlocks);
- const contentStateWithEntity = contentState.createEntity('DOCUMENT', 'MUTABLE', { title: 'Test title' });
- const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
- expect(shallow((
-
- Test children
-
- ))).toMatchSnapshot();
- });
-});
diff --git a/client/src/components/Draftail/decorators/Link.js b/client/src/components/Draftail/decorators/Link.js
index 7056a07eb..7f9652cbe 100644
--- a/client/src/components/Draftail/decorators/Link.js
+++ b/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 (
-
-
- {children}
-
+ }
+ label={url.replace(/(^\w+:|^)\/\//, '').split('/')[0]}
+ />
);
};
Link.propTypes = {
entityKey: PropTypes.string.isRequired,
contentState: PropTypes.object.isRequired,
- children: PropTypes.node.isRequired,
};
export default Link;
diff --git a/client/src/components/Draftail/decorators/Link.test.js b/client/src/components/Draftail/decorators/Link.test.js
deleted file mode 100644
index 5bc391c46..000000000
--- a/client/src/components/Draftail/decorators/Link.test.js
+++ /dev/null
@@ -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('aaaaaaaaaa
');
- const contentState = ContentState.createFromBlockArray(contentBlocks);
- const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'http://example.com/' });
- const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
- expect(shallow((
-
- Test children
-
- ))).toMatchSnapshot();
- });
-
- it('renders email', () => {
- const contentBlocks = convertFromHTML('aaaaaaaaaa
');
- const contentState = ContentState.createFromBlockArray(contentBlocks);
- const contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'mailto:test@example.com' });
- const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
- expect(shallow((
-
- Test children
-
- ))).toMatchSnapshot();
- });
-});
diff --git a/client/src/components/Draftail/decorators/TooltipEntity.js b/client/src/components/Draftail/decorators/TooltipEntity.js
new file mode 100644
index 000000000..14738df32
--- /dev/null
+++ b/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 (
+
+
+ {children}
+ {showTooltipAt && (
+
+
+
+ {label}
+
+
+
+
+
+
+
+ )}
+
+ );
+ }
+}
+
+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;
diff --git a/client/src/components/Draftail/decorators/TooltipEntity.scss b/client/src/components/Draftail/decorators/TooltipEntity.scss
new file mode 100644
index 000000000..abda1d6d4
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/client/src/components/Draftail/decorators/__snapshots__/Document.test.js.snap b/client/src/components/Draftail/decorators/__snapshots__/Document.test.js.snap
deleted file mode 100644
index ba3267df8..000000000
--- a/client/src/components/Draftail/decorators/__snapshots__/Document.test.js.snap
+++ /dev/null
@@ -1,18 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Document renders 1`] = `
-
-
-
- Test children
-
-
-`;
diff --git a/client/src/components/Draftail/decorators/__snapshots__/Link.test.js.snap b/client/src/components/Draftail/decorators/__snapshots__/Link.test.js.snap
deleted file mode 100644
index 59631823e..000000000
--- a/client/src/components/Draftail/decorators/__snapshots__/Link.test.js.snap
+++ /dev/null
@@ -1,33 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Link renders 1`] = `
-
-
-
- Test children
-
-
-`;
-
-exports[`Link renders email 1`] = `
-
-
-
- Test children
-
-
-`;
diff --git a/client/src/components/Draftail/decorators/index.js b/client/src/components/Draftail/decorators/index.js
deleted file mode 100644
index 0997c8d40..000000000
--- a/client/src/components/Draftail/decorators/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Link from './Link';
-import Document from './Document';
-
-/**
- * Mapping object from name to component.
- */
-export default {
- Link,
- Document,
-};
diff --git a/client/src/components/Draftail/decorators/index.test.js b/client/src/components/Draftail/decorators/index.test.js
deleted file mode 100644
index 521c0f419..000000000
--- a/client/src/components/Draftail/decorators/index.test.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import entities from './index';
-
-describe('entities', () => {
- it('exists', () => {
- expect(entities).toBeInstanceOf(Object);
- });
-});
diff --git a/client/src/components/Draftail/index.js b/client/src/components/Draftail/index.js
index 89dbef03a..a103327c8 100644
--- a/client/src/components/Draftail/index.js
+++ b/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;