From 750fbf5e3dcb5a1fe9bbe417d392a223d8b1ad82 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Thu, 27 Jul 2017 11:02:17 -0400 Subject: [PATCH] re-implement visual editor link button --- .../MarkdownControl/VisualEditor/index.js | 117 +++++++----------- 1 file changed, 48 insertions(+), 69 deletions(-) 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
{alt}
; }, 'table': props => {props.children}
, @@ -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}