diff --git a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
index 2406ceab..75f3072f 100644
--- a/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
+++ b/src/components/Widgets/Markdown/MarkdownControl/VisualEditor/index.js
@@ -13,58 +13,8 @@ import Toolbar from '../Toolbar/Toolbar';
import { Sticky } from '../../../../UI/Sticky/Sticky';
import styles from './index.css';
-
-function processUrl(url) {
- if (url.match(/^(https?:\/\/|mailto:|\/)/)) {
- return url;
- }
- if (url.match(/^[^/]+\.[^/]+/)) {
- return `https://${ url }`;
- }
- return `/${ url }`;
-}
-
const DEFAULT_NODE = 'paragraph';
-function schemaWithPlugins(schema, plugins) {
- let nodeSpec = schema.nodeSpec;
- plugins.forEach((plugin) => {
- const attrs = {};
- plugin.get('fields').forEach((field) => {
- attrs[field.get('name')] = { default: null };
- });
- nodeSpec = nodeSpec.addToEnd(`plugin_${ plugin.get('id') }`, {
- attrs,
- group: 'block',
- parseDOM: [{
- tag: 'div[data-plugin]',
- getAttrs(dom) {
- return JSON.parse(dom.getAttribute('data-plugin'));
- },
- }],
- toDOM(node) {
- return ['div', { 'data-plugin': JSON.stringify(node.attrs) }, plugin.get('label')];
- },
- });
- });
-
- return new Schema({
- nodes: nodeSpec,
- marks: schema.markSpec,
- });
-}
-
-function createSerializer(schema, plugins) {
- const serializer = Object.create(defaultMarkdownSerializer);
- plugins.forEach((plugin) => {
- serializer.nodes[`plugin_${ plugin.get('id') }`] = (state, node) => {
- const toBlock = plugin.get('toBlock');
- state.write(`${ toBlock.call(plugin, node.attrs) }\n\n`);
- };
- });
- return serializer;
-}
-
const BLOCK_TAGS = {
p: 'paragraph',
li: 'list-item',
@@ -109,9 +59,9 @@ const BLOCK_COMPONENTS = {
'heading-six': props =>
{props.children}
,
'image': props => {
const data = props.node && props.node.get('data');
- const src = data && data.get('src') || props.src;
- const alt = data && data.get('alt') || props.alt;
- const title = data && data.get('title') || props.title;
+ const src = data.get('url');
+ const alt = data.get('alt');
+ const title = data.get('title');
return ;
},
'table': props => ,
@@ -133,8 +83,8 @@ const NODE_COMPONENTS = {
...BLOCK_COMPONENTS,
'link': props => {
const data = props.node.get('data');
- const href = data && data.get('url') || props.href;
- const title = data && data.get('title') || props.title;
+ const href = data.get('url');
+ const title = data.get('title');
return {props.children};
},
'shortcode': props => {
@@ -386,14 +336,42 @@ export default class Editor extends Component {
this.setState({ editorState: resolvedState });
};
+ hasLinks = () => {
+ return this.state.editorState.inlines.some(inline => inline.type === 'link');
+ };
handleLink = () => {
- let url = null;
- if (!markActive(this.view.state, this.state.schema.marks.link)) {
- url = prompt('Link URL:'); // eslint-disable-line no-alert
+ let { editorState } = this.state;
+
+ // If the current selection contains links, clicking the "link" button
+ // should simply unlink them.
+ if (this.hasLinks()) {
+ editorState = editorState.transform().unwrapInline('link').apply();
}
- const command = toggleMark(this.state.schema.marks.link, { href: url ? processUrl(url) : null });
- command(this.view.state, this.handleAction);
+
+ else {
+ const url = window.prompt('Enter the URL of the link');
+
+ // If nothing is entered in the URL prompt, do nothing.
+ if (!url) return;
+
+ let transform = editorState.transform();
+
+ // If no text is selected, use the entered URL as text.
+ if (editorState.isCollapsed) {
+ transform = transform
+ .insertText(url)
+ .extend(0 - url.length);
+ }
+
+ editorState = transform
+ .wrapInline({ type: 'link', data: { url } })
+ .collapseToEnd()
+ .apply();
+ }
+
+ this.ref.onChange(editorState);
+ this.setState({ editorState });
};
handlePluginSubmit = (plugin, shortcodeData) => {
@@ -414,9 +392,10 @@ export default class Editor extends Component {
this.props.onMode('raw');
};
- getButtonProps = (type, isBlock) => {
- const handler = isBlock ? this.handleBlockClick: this.handleMarkClick;
- const isActive = isBlock ? this.hasBlock : this.hasMark;
+ getButtonProps = (type, opts = {}) => {
+ const { isBlock } = opts;
+ const handler = opts.handler || (isBlock ? this.handleBlockClick: this.handleMarkClick);
+ const isActive = opts.isActive || (isBlock ? this.hasBlock : this.hasMark);
return { onAction: e => handler(e, type), active: isActive(type) };
};
@@ -437,12 +416,12 @@ export default class Editor extends Component {
bold: this.getButtonProps('bold'),
italic: this.getButtonProps('italic'),
code: this.getButtonProps('code'),
- link: this.getButtonProps('link'),
- h1: this.getButtonProps('heading-one', true),
- h2: this.getButtonProps('heading-two', true),
- list: this.getButtonProps('bulleted-list', true),
- listNumbered: this.getButtonProps('numbered-list', true),
- codeBlock: this.getButtonProps('code', true),
+ link: this.getButtonProps('link', { handler: this.handleLink, isActive: this.hasLinks }),
+ h1: this.getButtonProps('heading-one', { isBlock: true }),
+ h2: this.getButtonProps('heading-two', { isBlock: true }),
+ list: this.getButtonProps('bulleted-list', { isBlock: true }),
+ listNumbered: this.getButtonProps('numbered-list', { isBlock: true }),
+ codeBlock: this.getButtonProps('code', { isBlock: true }),
}}
onToggleMode={this.handleToggle}
plugins={plugins}