re-implement visual editor link button
This commit is contained in:
parent
28ee67c35e
commit
750fbf5e3d
@ -13,58 +13,8 @@ import Toolbar from '../Toolbar/Toolbar';
|
|||||||
import { Sticky } from '../../../../UI/Sticky/Sticky';
|
import { Sticky } from '../../../../UI/Sticky/Sticky';
|
||||||
import styles from './index.css';
|
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';
|
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 = {
|
const BLOCK_TAGS = {
|
||||||
p: 'paragraph',
|
p: 'paragraph',
|
||||||
li: 'list-item',
|
li: 'list-item',
|
||||||
@ -109,9 +59,9 @@ const BLOCK_COMPONENTS = {
|
|||||||
'heading-six': props => <h6 {...props.attributes}>{props.children}</h6>,
|
'heading-six': props => <h6 {...props.attributes}>{props.children}</h6>,
|
||||||
'image': props => {
|
'image': props => {
|
||||||
const data = props.node && props.node.get('data');
|
const data = props.node && props.node.get('data');
|
||||||
const src = data && data.get('src') || props.src;
|
const src = data.get('url');
|
||||||
const alt = data && data.get('alt') || props.alt;
|
const alt = data.get('alt');
|
||||||
const title = data && data.get('title') || props.title;
|
const title = data.get('title');
|
||||||
return <div><img src={src} alt={alt} title={title}{...props.attributes}/></div>;
|
return <div><img src={src} alt={alt} title={title}{...props.attributes}/></div>;
|
||||||
},
|
},
|
||||||
'table': props => <table><tbody {...props.attributes}>{props.children}</tbody></table>,
|
'table': props => <table><tbody {...props.attributes}>{props.children}</tbody></table>,
|
||||||
@ -133,8 +83,8 @@ const NODE_COMPONENTS = {
|
|||||||
...BLOCK_COMPONENTS,
|
...BLOCK_COMPONENTS,
|
||||||
'link': props => {
|
'link': props => {
|
||||||
const data = props.node.get('data');
|
const data = props.node.get('data');
|
||||||
const href = data && data.get('url') || props.href;
|
const href = data.get('url');
|
||||||
const title = data && data.get('title') || props.title;
|
const title = data.get('title');
|
||||||
return <a href={href} title={title} {...props.attributes}>{props.children}</a>;
|
return <a href={href} title={title} {...props.attributes}>{props.children}</a>;
|
||||||
},
|
},
|
||||||
'shortcode': props => {
|
'shortcode': props => {
|
||||||
@ -386,14 +336,42 @@ export default class Editor extends Component {
|
|||||||
this.setState({ editorState: resolvedState });
|
this.setState({ editorState: resolvedState });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hasLinks = () => {
|
||||||
|
return this.state.editorState.inlines.some(inline => inline.type === 'link');
|
||||||
|
};
|
||||||
|
|
||||||
handleLink = () => {
|
handleLink = () => {
|
||||||
let url = null;
|
let { editorState } = this.state;
|
||||||
if (!markActive(this.view.state, this.state.schema.marks.link)) {
|
|
||||||
url = prompt('Link URL:'); // eslint-disable-line no-alert
|
// 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) => {
|
handlePluginSubmit = (plugin, shortcodeData) => {
|
||||||
@ -414,9 +392,10 @@ export default class Editor extends Component {
|
|||||||
this.props.onMode('raw');
|
this.props.onMode('raw');
|
||||||
};
|
};
|
||||||
|
|
||||||
getButtonProps = (type, isBlock) => {
|
getButtonProps = (type, opts = {}) => {
|
||||||
const handler = isBlock ? this.handleBlockClick: this.handleMarkClick;
|
const { isBlock } = opts;
|
||||||
const isActive = isBlock ? this.hasBlock : this.hasMark;
|
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) };
|
return { onAction: e => handler(e, type), active: isActive(type) };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -437,12 +416,12 @@ export default class Editor extends Component {
|
|||||||
bold: this.getButtonProps('bold'),
|
bold: this.getButtonProps('bold'),
|
||||||
italic: this.getButtonProps('italic'),
|
italic: this.getButtonProps('italic'),
|
||||||
code: this.getButtonProps('code'),
|
code: this.getButtonProps('code'),
|
||||||
link: this.getButtonProps('link'),
|
link: this.getButtonProps('link', { handler: this.handleLink, isActive: this.hasLinks }),
|
||||||
h1: this.getButtonProps('heading-one', true),
|
h1: this.getButtonProps('heading-one', { isBlock: true }),
|
||||||
h2: this.getButtonProps('heading-two', true),
|
h2: this.getButtonProps('heading-two', { isBlock: true }),
|
||||||
list: this.getButtonProps('bulleted-list', true),
|
list: this.getButtonProps('bulleted-list', { isBlock: true }),
|
||||||
listNumbered: this.getButtonProps('numbered-list', true),
|
listNumbered: this.getButtonProps('numbered-list', { isBlock: true }),
|
||||||
codeBlock: this.getButtonProps('code', true),
|
codeBlock: this.getButtonProps('code', { isBlock: true }),
|
||||||
}}
|
}}
|
||||||
onToggleMode={this.handleToggle}
|
onToggleMode={this.handleToggle}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user