refactor: convert function expressions to declarations (#4926)
This commit is contained in:
committed by
GitHub
parent
c0236536dd
commit
141a2eba56
@ -13,7 +13,8 @@ import { markdownToHtml } from '../serializers';
|
||||
import { editorStyleVars, EditorControlBar } from '../styles';
|
||||
import Toolbar from './Toolbar';
|
||||
|
||||
const rawEditorStyles = ({ minimal }) => `
|
||||
function rawEditorStyles({ minimal }) {
|
||||
return `
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
@ -24,6 +25,7 @@ const rawEditorStyles = ({ minimal }) => `
|
||||
border-top: 0;
|
||||
margin-top: -${editorStyleVars.stickyDistanceBottom};
|
||||
`;
|
||||
}
|
||||
|
||||
const RawEditorContainer = styled.div`
|
||||
position: relative;
|
||||
|
@ -113,7 +113,11 @@ export default class Toolbar extends React.Component {
|
||||
} = this.props;
|
||||
const isVisible = this.isVisible;
|
||||
const showEditorComponents = !editorComponents || editorComponents.size >= 1;
|
||||
const showPlugin = ({ id }) => (editorComponents ? editorComponents.includes(id) : true);
|
||||
|
||||
function showPlugin({ id }) {
|
||||
return editorComponents ? editorComponents.includes(id) : true;
|
||||
}
|
||||
|
||||
const pluginsList = plugins ? plugins.toList().filter(showPlugin) : List();
|
||||
|
||||
const headingOptions = {
|
||||
|
@ -23,16 +23,18 @@ const StyledToolbarButton = styled.button`
|
||||
}
|
||||
`;
|
||||
|
||||
const ToolbarButton = ({ type, label, icon, onClick, isActive, disabled }) => (
|
||||
<StyledToolbarButton
|
||||
isActive={isActive}
|
||||
onClick={e => onClick && onClick(e, type)}
|
||||
title={label}
|
||||
disabled={disabled}
|
||||
>
|
||||
{icon ? <Icon type={icon} /> : label}
|
||||
</StyledToolbarButton>
|
||||
);
|
||||
function ToolbarButton({ type, label, icon, onClick, isActive, disabled }) {
|
||||
return (
|
||||
<StyledToolbarButton
|
||||
isActive={isActive}
|
||||
onClick={e => onClick && onClick(e, type)}
|
||||
title={label}
|
||||
disabled={disabled}
|
||||
>
|
||||
{icon ? <Icon type={icon} /> : label}
|
||||
</StyledToolbarButton>
|
||||
);
|
||||
}
|
||||
|
||||
ToolbarButton.propTypes = {
|
||||
type: PropTypes.string,
|
||||
|
@ -15,7 +15,8 @@ import { renderBlock, renderInline, renderMark } from './renderers';
|
||||
import plugins from './plugins/visual';
|
||||
import schema from './schema';
|
||||
|
||||
const visualEditorStyles = ({ minimal }) => `
|
||||
function visualEditorStyles({ minimal }) {
|
||||
return `
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow-x: auto;
|
||||
@ -30,26 +31,27 @@ const visualEditorStyles = ({ minimal }) => `
|
||||
flex-direction: column;
|
||||
z-index: ${zIndex.zIndex100};
|
||||
`;
|
||||
}
|
||||
|
||||
const InsertionPoint = styled.div`
|
||||
flex: 1 1 auto;
|
||||
cursor: text;
|
||||
`;
|
||||
|
||||
const createEmptyRawDoc = () => {
|
||||
function createEmptyRawDoc() {
|
||||
const emptyText = Text.create('');
|
||||
const emptyBlock = Block.create({ object: 'block', type: 'paragraph', nodes: [emptyText] });
|
||||
return { nodes: [emptyBlock] };
|
||||
};
|
||||
}
|
||||
|
||||
const createSlateValue = (rawValue, { voidCodeBlock }) => {
|
||||
function createSlateValue(rawValue, { voidCodeBlock }) {
|
||||
const rawDoc = rawValue && markdownToSlate(rawValue, { voidCodeBlock });
|
||||
const rawDocHasNodes = !isEmpty(get(rawDoc, 'nodes'));
|
||||
const document = Document.fromJSON(rawDocHasNodes ? rawDoc : createEmptyRawDoc());
|
||||
return Value.create({ document });
|
||||
};
|
||||
}
|
||||
|
||||
export const mergeMediaConfig = (editorComponents, field) => {
|
||||
export function mergeMediaConfig(editorComponents, field) {
|
||||
// merge editor media library config to image components
|
||||
if (editorComponents.has('image')) {
|
||||
const imageComponent = editorComponents.get('image');
|
||||
@ -79,7 +81,7 @@ export const mergeMediaConfig = (editorComponents, field) => {
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default class Editor extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -38,12 +38,14 @@ describe.skip('slate', () => {
|
||||
</document>
|
||||
</value>
|
||||
);
|
||||
const fn = editor => {
|
||||
|
||||
function fn(editor) {
|
||||
editor
|
||||
.deleteBackward()
|
||||
.insertText('b')
|
||||
.setBlocks('heading-one');
|
||||
};
|
||||
}
|
||||
|
||||
const [actual, expected] = run(input, output, fn);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
@ -24,12 +24,14 @@ export default class Shortcode extends React.Component {
|
||||
const EditorControl = getEditorControl();
|
||||
const value = dataKey === false ? node.data : fromJS(node.data.get(dataKey));
|
||||
|
||||
const handleChange = (fieldName, value, metadata) => {
|
||||
function handleChange(fieldName, value, metadata) {
|
||||
const dataValue = dataKey === false ? value : node.data.set('shortcodeData', value);
|
||||
editor.setNodeByKey(node.key, { data: dataValue || Map(), metadata });
|
||||
};
|
||||
}
|
||||
|
||||
const handleFocus = () => editor.moveToRangeOfNode(node);
|
||||
function handleFocus() {
|
||||
return editor.moveToRangeOfNode(node);
|
||||
}
|
||||
|
||||
return (
|
||||
!field.isEmpty() && (
|
||||
|
@ -3,23 +3,25 @@ import React from 'react';
|
||||
import { css } from '@emotion/core';
|
||||
import { zIndex } from 'netlify-cms-ui-default';
|
||||
|
||||
const InsertionPoint = props => (
|
||||
<div
|
||||
css={css`
|
||||
height: 32px;
|
||||
cursor: text;
|
||||
position: relative;
|
||||
z-index: ${zIndex.zIndex1};
|
||||
margin-top: -16px;
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
function InsertionPoint(props) {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
height: 32px;
|
||||
cursor: text;
|
||||
position: relative;
|
||||
z-index: ${zIndex.zIndex1};
|
||||
margin-top: -16px;
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const VoidBlock = ({ editor, attributes, node, children }) => {
|
||||
const handleClick = event => {
|
||||
function VoidBlock({ editor, attributes, node, children }) {
|
||||
function handleClick(event) {
|
||||
event.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...attributes} onClick={handleClick}>
|
||||
@ -32,6 +34,6 @@ const VoidBlock = ({ editor, attributes, node, children }) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default VoidBlock;
|
||||
|
@ -10,10 +10,16 @@ const MODE_STORAGE_KEY = 'cms.md-mode';
|
||||
// TODO: passing the editorControl and components like this is horrible, should
|
||||
// be handled through Redux and a separate registry store for instances
|
||||
let editorControl;
|
||||
// eslint-disable-next-line func-style
|
||||
let _getEditorComponents = () => [];
|
||||
|
||||
export const getEditorControl = () => editorControl;
|
||||
export const getEditorComponents = () => _getEditorComponents();
|
||||
export function getEditorControl() {
|
||||
return editorControl;
|
||||
}
|
||||
|
||||
export function getEditorComponents() {
|
||||
return _getEditorComponents();
|
||||
}
|
||||
|
||||
export default class MarkdownControl extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -1,21 +1,23 @@
|
||||
import isHotkey from 'is-hotkey';
|
||||
|
||||
const BreakToDefaultBlock = ({ defaultType }) => ({
|
||||
onKeyDown(event, editor, next) {
|
||||
const { selection, startBlock } = editor.value;
|
||||
const isEnter = isHotkey('enter', event);
|
||||
if (!isEnter) {
|
||||
function BreakToDefaultBlock({ defaultType }) {
|
||||
return {
|
||||
onKeyDown(event, editor, next) {
|
||||
const { selection, startBlock } = editor.value;
|
||||
const isEnter = isHotkey('enter', event);
|
||||
if (!isEnter) {
|
||||
return next();
|
||||
}
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
return next();
|
||||
}
|
||||
if (selection.start.isAtEndOfNode(startBlock) && startBlock.type !== defaultType) {
|
||||
return editor.insertBlock(defaultType);
|
||||
}
|
||||
return next();
|
||||
}
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
return next();
|
||||
}
|
||||
if (selection.start.isAtEndOfNode(startBlock) && startBlock.type !== defaultType) {
|
||||
return editor.insertBlock(defaultType);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default BreakToDefaultBlock;
|
||||
|
@ -1,23 +1,25 @@
|
||||
import isHotkey from 'is-hotkey';
|
||||
|
||||
const CloseBlock = ({ defaultType }) => ({
|
||||
onKeyDown(event, editor, next) {
|
||||
const { selection, startBlock } = editor.value;
|
||||
const isBackspace = isHotkey('backspace', event);
|
||||
if (!isBackspace) {
|
||||
function CloseBlock({ defaultType }) {
|
||||
return {
|
||||
onKeyDown(event, editor, next) {
|
||||
const { selection, startBlock } = editor.value;
|
||||
const isBackspace = isHotkey('backspace', event);
|
||||
if (!isBackspace) {
|
||||
return next();
|
||||
}
|
||||
if (selection.isExpanded) {
|
||||
return editor.delete();
|
||||
}
|
||||
if (!selection.start.isAtStartOfNode(startBlock) || startBlock.text.length > 0) {
|
||||
return next();
|
||||
}
|
||||
if (startBlock.type !== defaultType) {
|
||||
return editor.setBlocks(defaultType);
|
||||
}
|
||||
return next();
|
||||
}
|
||||
if (selection.isExpanded) {
|
||||
return editor.delete();
|
||||
}
|
||||
if (!selection.start.isAtStartOfNode(startBlock) || startBlock.text.length > 0) {
|
||||
return next();
|
||||
}
|
||||
if (startBlock.type !== defaultType) {
|
||||
return editor.setBlocks(defaultType);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default CloseBlock;
|
||||
|
@ -1,121 +1,123 @@
|
||||
import { isArray, tail, castArray } from 'lodash';
|
||||
|
||||
const CommandsAndQueries = ({ defaultType }) => ({
|
||||
queries: {
|
||||
atStartOf(editor, node) {
|
||||
const { selection } = editor.value;
|
||||
return selection.isCollapsed && selection.start.isAtStartOfNode(node);
|
||||
},
|
||||
getAncestor(editor, firstKey, lastKey) {
|
||||
if (firstKey === lastKey) {
|
||||
return editor.value.document.getParent(firstKey);
|
||||
}
|
||||
return editor.value.document.getCommonAncestor(firstKey, lastKey);
|
||||
},
|
||||
getOffset(editor, node) {
|
||||
const parent = editor.value.document.getParent(node.key);
|
||||
return parent.nodes.indexOf(node);
|
||||
},
|
||||
getSelectedChildren(editor, node) {
|
||||
return node.nodes.filter(child => editor.isSelected(child));
|
||||
},
|
||||
getCommonAncestor(editor) {
|
||||
const { startBlock, endBlock, document: doc } = editor.value;
|
||||
return doc.getCommonAncestor(startBlock.key, endBlock.key);
|
||||
},
|
||||
getClosestType(editor, node, type) {
|
||||
const types = castArray(type);
|
||||
return editor.value.document.getClosest(node.key, n => types.includes(n.type));
|
||||
},
|
||||
getBlockContainer(editor, node) {
|
||||
const targetTypes = ['bulleted-list', 'numbered-list', 'list-item', 'quote', 'table-cell'];
|
||||
const { startBlock, selection } = editor.value;
|
||||
const target = node
|
||||
? editor.value.document.getParent(node.key)
|
||||
: (selection.isCollapsed && startBlock) || editor.getCommonAncestor();
|
||||
if (!target) {
|
||||
return editor.value.document;
|
||||
}
|
||||
if (targetTypes.includes(target.type)) {
|
||||
return target;
|
||||
}
|
||||
return editor.getBlockContainer(target);
|
||||
},
|
||||
isSelected(editor, nodes) {
|
||||
return castArray(nodes).every(node => {
|
||||
return editor.value.document.isInRange(node.key, editor.value.selection);
|
||||
});
|
||||
},
|
||||
isFirstChild(editor, node) {
|
||||
return editor.value.document.getParent(node.key).nodes.first().key === node.key;
|
||||
},
|
||||
areSiblings(editor, nodes) {
|
||||
if (!isArray(nodes) || nodes.length < 2) {
|
||||
return true;
|
||||
}
|
||||
const parent = editor.value.document.getParent(nodes[0].key);
|
||||
return tail(nodes).every(node => {
|
||||
return editor.value.document.getParent(node.key).key === parent.key;
|
||||
});
|
||||
},
|
||||
everyBlock(editor, type) {
|
||||
return editor.value.blocks.every(block => block.type === type);
|
||||
},
|
||||
hasMark(editor, type) {
|
||||
return editor.value.activeMarks.some(mark => mark.type === type);
|
||||
},
|
||||
hasBlock(editor, type) {
|
||||
return editor.value.blocks.some(node => node.type === type);
|
||||
},
|
||||
hasInline(editor, type) {
|
||||
return editor.value.inlines.some(node => node.type === type);
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
toggleBlock(editor, type) {
|
||||
switch (type) {
|
||||
case 'heading-one':
|
||||
case 'heading-two':
|
||||
case 'heading-three':
|
||||
case 'heading-four':
|
||||
case 'heading-five':
|
||||
case 'heading-six':
|
||||
return editor.setBlocks(editor.everyBlock(type) ? defaultType : type);
|
||||
case 'quote':
|
||||
return editor.toggleQuoteBlock();
|
||||
case 'numbered-list':
|
||||
case 'bulleted-list': {
|
||||
return editor.toggleList(type);
|
||||
function CommandsAndQueries({ defaultType }) {
|
||||
return {
|
||||
queries: {
|
||||
atStartOf(editor, node) {
|
||||
const { selection } = editor.value;
|
||||
return selection.isCollapsed && selection.start.isAtStartOfNode(node);
|
||||
},
|
||||
getAncestor(editor, firstKey, lastKey) {
|
||||
if (firstKey === lastKey) {
|
||||
return editor.value.document.getParent(firstKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
unwrapBlockChildren(editor, block) {
|
||||
if (!block || block.object !== 'block') {
|
||||
throw Error(`Expected block but received ${block}.`);
|
||||
}
|
||||
const index = editor.value.document.getPath(block.key).last();
|
||||
const parent = editor.value.document.getParent(block.key);
|
||||
editor.withoutNormalizing(() => {
|
||||
block.nodes.forEach((node, idx) => {
|
||||
editor.moveNodeByKey(node.key, parent.key, index + idx);
|
||||
return editor.value.document.getCommonAncestor(firstKey, lastKey);
|
||||
},
|
||||
getOffset(editor, node) {
|
||||
const parent = editor.value.document.getParent(node.key);
|
||||
return parent.nodes.indexOf(node);
|
||||
},
|
||||
getSelectedChildren(editor, node) {
|
||||
return node.nodes.filter(child => editor.isSelected(child));
|
||||
},
|
||||
getCommonAncestor(editor) {
|
||||
const { startBlock, endBlock, document: doc } = editor.value;
|
||||
return doc.getCommonAncestor(startBlock.key, endBlock.key);
|
||||
},
|
||||
getClosestType(editor, node, type) {
|
||||
const types = castArray(type);
|
||||
return editor.value.document.getClosest(node.key, n => types.includes(n.type));
|
||||
},
|
||||
getBlockContainer(editor, node) {
|
||||
const targetTypes = ['bulleted-list', 'numbered-list', 'list-item', 'quote', 'table-cell'];
|
||||
const { startBlock, selection } = editor.value;
|
||||
const target = node
|
||||
? editor.value.document.getParent(node.key)
|
||||
: (selection.isCollapsed && startBlock) || editor.getCommonAncestor();
|
||||
if (!target) {
|
||||
return editor.value.document;
|
||||
}
|
||||
if (targetTypes.includes(target.type)) {
|
||||
return target;
|
||||
}
|
||||
return editor.getBlockContainer(target);
|
||||
},
|
||||
isSelected(editor, nodes) {
|
||||
return castArray(nodes).every(node => {
|
||||
return editor.value.document.isInRange(node.key, editor.value.selection);
|
||||
});
|
||||
editor.removeNodeByKey(block.key);
|
||||
});
|
||||
},
|
||||
unwrapNodeToDepth(editor, node, depth) {
|
||||
let currentDepth = 0;
|
||||
editor.withoutNormalizing(() => {
|
||||
while (currentDepth < depth) {
|
||||
editor.unwrapNodeByKey(node.key);
|
||||
currentDepth += 1;
|
||||
},
|
||||
isFirstChild(editor, node) {
|
||||
return editor.value.document.getParent(node.key).nodes.first().key === node.key;
|
||||
},
|
||||
areSiblings(editor, nodes) {
|
||||
if (!isArray(nodes) || nodes.length < 2) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const parent = editor.value.document.getParent(nodes[0].key);
|
||||
return tail(nodes).every(node => {
|
||||
return editor.value.document.getParent(node.key).key === parent.key;
|
||||
});
|
||||
},
|
||||
everyBlock(editor, type) {
|
||||
return editor.value.blocks.every(block => block.type === type);
|
||||
},
|
||||
hasMark(editor, type) {
|
||||
return editor.value.activeMarks.some(mark => mark.type === type);
|
||||
},
|
||||
hasBlock(editor, type) {
|
||||
return editor.value.blocks.some(node => node.type === type);
|
||||
},
|
||||
hasInline(editor, type) {
|
||||
return editor.value.inlines.some(node => node.type === type);
|
||||
},
|
||||
},
|
||||
unwrapNodeFromAncestor(editor, node, ancestor) {
|
||||
const depth = ancestor.getDepth(node.key);
|
||||
editor.unwrapNodeToDepth(node, depth);
|
||||
commands: {
|
||||
toggleBlock(editor, type) {
|
||||
switch (type) {
|
||||
case 'heading-one':
|
||||
case 'heading-two':
|
||||
case 'heading-three':
|
||||
case 'heading-four':
|
||||
case 'heading-five':
|
||||
case 'heading-six':
|
||||
return editor.setBlocks(editor.everyBlock(type) ? defaultType : type);
|
||||
case 'quote':
|
||||
return editor.toggleQuoteBlock();
|
||||
case 'numbered-list':
|
||||
case 'bulleted-list': {
|
||||
return editor.toggleList(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
unwrapBlockChildren(editor, block) {
|
||||
if (!block || block.object !== 'block') {
|
||||
throw Error(`Expected block but received ${block}.`);
|
||||
}
|
||||
const index = editor.value.document.getPath(block.key).last();
|
||||
const parent = editor.value.document.getParent(block.key);
|
||||
editor.withoutNormalizing(() => {
|
||||
block.nodes.forEach((node, idx) => {
|
||||
editor.moveNodeByKey(node.key, parent.key, index + idx);
|
||||
});
|
||||
editor.removeNodeByKey(block.key);
|
||||
});
|
||||
},
|
||||
unwrapNodeToDepth(editor, node, depth) {
|
||||
let currentDepth = 0;
|
||||
editor.withoutNormalizing(() => {
|
||||
while (currentDepth < depth) {
|
||||
editor.unwrapNodeByKey(node.key);
|
||||
currentDepth += 1;
|
||||
}
|
||||
});
|
||||
},
|
||||
unwrapNodeFromAncestor(editor, node, ancestor) {
|
||||
const depth = ancestor.getDepth(node.key);
|
||||
editor.unwrapNodeToDepth(node, depth);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default CommandsAndQueries;
|
||||
|
@ -4,15 +4,15 @@ import base64 from 'slate-base64-serializer';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { slateToMarkdown, markdownToSlate, htmlToSlate, markdownToHtml } from '../../serializers';
|
||||
|
||||
const CopyPasteVisual = ({ getAsset, resolveWidget }) => {
|
||||
const handleCopy = (event, editor) => {
|
||||
function CopyPasteVisual({ getAsset, resolveWidget }) {
|
||||
function handleCopy(event, editor) {
|
||||
const markdown = slateToMarkdown(editor.value.fragment.toJS());
|
||||
const html = markdownToHtml(markdown, { getAsset, resolveWidget });
|
||||
setEventTransfer(event, 'text', markdown);
|
||||
setEventTransfer(event, 'html', html);
|
||||
setEventTransfer(event, 'fragment', base64.serializeNode(editor.value.fragment));
|
||||
event.preventDefault();
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
onPaste(event, editor, next) {
|
||||
@ -39,6 +39,6 @@ const CopyPasteVisual = ({ getAsset, resolveWidget }) => {
|
||||
editor.delete();
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default CopyPasteVisual;
|
||||
|
@ -1,42 +1,44 @@
|
||||
const ForceInsert = ({ defaultType }) => ({
|
||||
queries: {
|
||||
canInsertBeforeNode(editor, node) {
|
||||
if (!editor.isVoid(node)) {
|
||||
return true;
|
||||
}
|
||||
return !!editor.value.document.getPreviousSibling(node.key);
|
||||
function ForceInsert({ defaultType }) {
|
||||
return {
|
||||
queries: {
|
||||
canInsertBeforeNode(editor, node) {
|
||||
if (!editor.isVoid(node)) {
|
||||
return true;
|
||||
}
|
||||
return !!editor.value.document.getPreviousSibling(node.key);
|
||||
},
|
||||
canInsertAfterNode(editor, node) {
|
||||
if (!editor.isVoid(node)) {
|
||||
return true;
|
||||
}
|
||||
const nextSibling = editor.value.document.getNextSibling(node.key);
|
||||
return nextSibling && !editor.isVoid(nextSibling);
|
||||
},
|
||||
},
|
||||
canInsertAfterNode(editor, node) {
|
||||
if (!editor.isVoid(node)) {
|
||||
return true;
|
||||
}
|
||||
const nextSibling = editor.value.document.getNextSibling(node.key);
|
||||
return nextSibling && !editor.isVoid(nextSibling);
|
||||
commands: {
|
||||
forceInsertBeforeNode(editor, node) {
|
||||
const block = { type: defaultType, object: 'block' };
|
||||
const parent = editor.value.document.getParent(node.key);
|
||||
return editor
|
||||
.insertNodeByKey(parent.key, 0, block)
|
||||
.moveToStartOfNode(parent)
|
||||
.focus();
|
||||
},
|
||||
forceInsertAfterNode(editor, node) {
|
||||
return editor
|
||||
.moveToEndOfNode(node)
|
||||
.insertBlock(defaultType)
|
||||
.focus();
|
||||
},
|
||||
moveToEndOfDocument(editor) {
|
||||
const lastBlock = editor.value.document.nodes.last();
|
||||
if (editor.isVoid(lastBlock)) {
|
||||
editor.insertBlock(defaultType);
|
||||
}
|
||||
return editor.moveToEndOfNode(lastBlock).focus();
|
||||
},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
forceInsertBeforeNode(editor, node) {
|
||||
const block = { type: defaultType, object: 'block' };
|
||||
const parent = editor.value.document.getParent(node.key);
|
||||
return editor
|
||||
.insertNodeByKey(parent.key, 0, block)
|
||||
.moveToStartOfNode(parent)
|
||||
.focus();
|
||||
},
|
||||
forceInsertAfterNode(editor, node) {
|
||||
return editor
|
||||
.moveToEndOfNode(node)
|
||||
.insertBlock(defaultType)
|
||||
.focus();
|
||||
},
|
||||
moveToEndOfDocument(editor) {
|
||||
const lastBlock = editor.value.document.nodes.last();
|
||||
if (editor.isVoid(lastBlock)) {
|
||||
editor.insertBlock(defaultType);
|
||||
}
|
||||
return editor.moveToEndOfNode(lastBlock).focus();
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ForceInsert;
|
||||
|
@ -14,7 +14,7 @@ export const HOT_KEY_MAP = {
|
||||
link: 'mod+k',
|
||||
};
|
||||
|
||||
const Hotkey = (key, fn) => {
|
||||
function Hotkey(key, fn) {
|
||||
return {
|
||||
onKeyDown(event, editor, next) {
|
||||
if (!isHotkey(key, event)) {
|
||||
@ -24,6 +24,6 @@ const Hotkey = (key, fn) => {
|
||||
editor.command(fn);
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default Hotkey;
|
||||
|
@ -1,16 +1,18 @@
|
||||
import isHotkey from 'is-hotkey';
|
||||
|
||||
const LineBreak = () => ({
|
||||
onKeyDown(event, editor, next) {
|
||||
const isShiftEnter = isHotkey('shift+enter', event);
|
||||
if (!isShiftEnter) {
|
||||
return next();
|
||||
}
|
||||
return editor
|
||||
.insertInline('break')
|
||||
.insertText('')
|
||||
.moveToStartOfNextText();
|
||||
},
|
||||
});
|
||||
function LineBreak() {
|
||||
return {
|
||||
onKeyDown(event, editor, next) {
|
||||
const isShiftEnter = isHotkey('shift+enter', event);
|
||||
if (!isShiftEnter) {
|
||||
return next();
|
||||
}
|
||||
return editor
|
||||
.insertInline('break')
|
||||
.insertText('')
|
||||
.moveToStartOfNextText();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default LineBreak;
|
||||
|
@ -1,27 +1,29 @@
|
||||
const Link = ({ type }) => ({
|
||||
commands: {
|
||||
toggleLink(editor, getUrl) {
|
||||
if (editor.hasInline(type)) {
|
||||
editor.unwrapInline(type);
|
||||
} else {
|
||||
const url = getUrl();
|
||||
if (!url) return;
|
||||
|
||||
const selection = editor.value.selection;
|
||||
const isCollapsed = selection && selection.isCollapsed;
|
||||
if (isCollapsed) {
|
||||
// If no text is selected, use the entered URL as text.
|
||||
return editor.insertInline({
|
||||
type,
|
||||
data: { url },
|
||||
nodes: [{ object: 'text', text: url }],
|
||||
});
|
||||
function Link({ type }) {
|
||||
return {
|
||||
commands: {
|
||||
toggleLink(editor, getUrl) {
|
||||
if (editor.hasInline(type)) {
|
||||
editor.unwrapInline(type);
|
||||
} else {
|
||||
return editor.wrapInline({ type, data: { url } }).moveToEnd();
|
||||
const url = getUrl();
|
||||
if (!url) return;
|
||||
|
||||
const selection = editor.value.selection;
|
||||
const isCollapsed = selection && selection.isCollapsed;
|
||||
if (isCollapsed) {
|
||||
// If no text is selected, use the entered URL as text.
|
||||
return editor.insertInline({
|
||||
type,
|
||||
data: { url },
|
||||
nodes: [{ object: 'text', text: url }],
|
||||
});
|
||||
} else {
|
||||
return editor.wrapInline({ type, data: { url } }).moveToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default Link;
|
||||
|
@ -4,7 +4,7 @@ import { Range, Block } from 'slate';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { assertType } from './util';
|
||||
|
||||
const ListPlugin = ({ defaultType, unorderedListType, orderedListType }) => {
|
||||
function ListPlugin({ defaultType, unorderedListType, orderedListType }) {
|
||||
const LIST_TYPES = [orderedListType, unorderedListType];
|
||||
|
||||
function oppositeListType(type) {
|
||||
@ -297,6 +297,6 @@ const ListPlugin = ({ defaultType, unorderedListType, orderedListType }) => {
|
||||
return next();
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default ListPlugin;
|
||||
|
@ -4,98 +4,100 @@ import isHotkey from 'is-hotkey';
|
||||
/**
|
||||
* TODO: highlight a couple list items and hit the quote button. doesn't work.
|
||||
*/
|
||||
const QuoteBlock = ({ type }) => ({
|
||||
commands: {
|
||||
/**
|
||||
* Quotes can contain other blocks, even other quotes. If a selection contains quotes, they
|
||||
* shouldn't be impacted. The selection's immediate parent should be checked - if it's a
|
||||
* quote, unwrap the quote (as within are only blocks), and if it's not, wrap all selected
|
||||
* blocks into a quote. Make sure text is wrapped into paragraphs.
|
||||
*/
|
||||
toggleQuoteBlock(editor) {
|
||||
const blockContainer = editor.getBlockContainer();
|
||||
if (['bulleted-list', 'numbered-list'].includes(blockContainer.type)) {
|
||||
const { nodes } = blockContainer;
|
||||
const allItemsSelected = editor.isSelected([nodes.first(), nodes.last()]);
|
||||
if (allItemsSelected) {
|
||||
const nextContainer = editor.getBlockContainer(blockContainer);
|
||||
if (nextContainer?.type === type) {
|
||||
editor.unwrapNodeFromAncestor(blockContainer, nextContainer);
|
||||
function QuoteBlock({ type }) {
|
||||
return {
|
||||
commands: {
|
||||
/**
|
||||
* Quotes can contain other blocks, even other quotes. If a selection contains quotes, they
|
||||
* shouldn't be impacted. The selection's immediate parent should be checked - if it's a
|
||||
* quote, unwrap the quote (as within are only blocks), and if it's not, wrap all selected
|
||||
* blocks into a quote. Make sure text is wrapped into paragraphs.
|
||||
*/
|
||||
toggleQuoteBlock(editor) {
|
||||
const blockContainer = editor.getBlockContainer();
|
||||
if (['bulleted-list', 'numbered-list'].includes(blockContainer.type)) {
|
||||
const { nodes } = blockContainer;
|
||||
const allItemsSelected = editor.isSelected([nodes.first(), nodes.last()]);
|
||||
if (allItemsSelected) {
|
||||
const nextContainer = editor.getBlockContainer(blockContainer);
|
||||
if (nextContainer?.type === type) {
|
||||
editor.unwrapNodeFromAncestor(blockContainer, nextContainer);
|
||||
} else {
|
||||
editor.wrapBlockByKey(blockContainer.key, type);
|
||||
}
|
||||
} else {
|
||||
editor.wrapBlockByKey(blockContainer.key, type);
|
||||
const blockContainerParent = editor.value.document.getParent(blockContainer.key);
|
||||
editor.withoutNormalizing(() => {
|
||||
const selectedListItems = nodes.filter(node => editor.isSelected(node));
|
||||
const newList = Block.create(blockContainer.type);
|
||||
editor.unwrapNodeByKey(selectedListItems.first());
|
||||
const offset = editor.getOffset(selectedListItems.first());
|
||||
editor.insertNodeByKey(blockContainerParent.key, offset + 1, newList);
|
||||
selectedListItems.forEach(({ key }, idx) =>
|
||||
editor.moveNodeByKey(key, newList.key, idx),
|
||||
);
|
||||
editor.wrapBlockByKey(newList.key, type);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const blockContainerParent = editor.value.document.getParent(blockContainer.key);
|
||||
editor.withoutNormalizing(() => {
|
||||
const selectedListItems = nodes.filter(node => editor.isSelected(node));
|
||||
const newList = Block.create(blockContainer.type);
|
||||
editor.unwrapNodeByKey(selectedListItems.first());
|
||||
const offset = editor.getOffset(selectedListItems.first());
|
||||
editor.insertNodeByKey(blockContainerParent.key, offset + 1, newList);
|
||||
selectedListItems.forEach(({ key }, idx) =>
|
||||
editor.moveNodeByKey(key, newList.key, idx),
|
||||
);
|
||||
editor.wrapBlockByKey(newList.key, type);
|
||||
});
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const blocks = editor.value.blocks;
|
||||
const firstBlockKey = blocks.first().key;
|
||||
const lastBlockKey = blocks.last().key;
|
||||
const ancestor = editor.getAncestor(firstBlockKey, lastBlockKey);
|
||||
if (ancestor.type === type) {
|
||||
editor.unwrapBlockChildren(ancestor);
|
||||
} else {
|
||||
editor.wrapBlock(type);
|
||||
}
|
||||
const blocks = editor.value.blocks;
|
||||
const firstBlockKey = blocks.first().key;
|
||||
const lastBlockKey = blocks.last().key;
|
||||
const ancestor = editor.getAncestor(firstBlockKey, lastBlockKey);
|
||||
if (ancestor.type === type) {
|
||||
editor.unwrapBlockChildren(ancestor);
|
||||
} else {
|
||||
editor.wrapBlock(type);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
onKeyDown(event, editor, next) {
|
||||
if (!isHotkey('enter', event) && !isHotkey('backspace', event)) {
|
||||
return next();
|
||||
}
|
||||
const { selection, startBlock, document: doc } = editor.value;
|
||||
const parent = doc.getParent(startBlock.key);
|
||||
const isQuote = parent.type === type;
|
||||
if (!isQuote) {
|
||||
return next();
|
||||
}
|
||||
if (isHotkey('enter', event)) {
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
}
|
||||
|
||||
// If the quote is empty, remove it.
|
||||
if (editor.atStartOf(parent)) {
|
||||
return editor.unwrapBlockByKey(parent.key);
|
||||
}
|
||||
|
||||
if (editor.atStartOf(startBlock)) {
|
||||
const offset = editor.getOffset(startBlock);
|
||||
return editor
|
||||
.splitNodeByKey(parent.key, offset)
|
||||
.unwrapBlockByKey(editor.value.document.getParent(startBlock.key).key);
|
||||
}
|
||||
|
||||
return next();
|
||||
} else if (isHotkey('backspace', event)) {
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
}
|
||||
if (!editor.atStartOf(parent)) {
|
||||
onKeyDown(event, editor, next) {
|
||||
if (!isHotkey('enter', event) && !isHotkey('backspace', event)) {
|
||||
return next();
|
||||
}
|
||||
const previousParentSibling = doc.getPreviousSibling(parent.key);
|
||||
if (previousParentSibling && previousParentSibling.type === type) {
|
||||
return editor.mergeNodeByKey(parent.key);
|
||||
const { selection, startBlock, document: doc } = editor.value;
|
||||
const parent = doc.getParent(startBlock.key);
|
||||
const isQuote = parent.type === type;
|
||||
if (!isQuote) {
|
||||
return next();
|
||||
}
|
||||
if (isHotkey('enter', event)) {
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
}
|
||||
|
||||
return editor.unwrapNodeByKey(startBlock.key);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
});
|
||||
// If the quote is empty, remove it.
|
||||
if (editor.atStartOf(parent)) {
|
||||
return editor.unwrapBlockByKey(parent.key);
|
||||
}
|
||||
|
||||
if (editor.atStartOf(startBlock)) {
|
||||
const offset = editor.getOffset(startBlock);
|
||||
return editor
|
||||
.splitNodeByKey(parent.key, offset)
|
||||
.unwrapBlockByKey(editor.value.document.getParent(startBlock.key).key);
|
||||
}
|
||||
|
||||
return next();
|
||||
} else if (isHotkey('backspace', event)) {
|
||||
if (selection.isExpanded) {
|
||||
editor.delete();
|
||||
}
|
||||
if (!editor.atStartOf(parent)) {
|
||||
return next();
|
||||
}
|
||||
const previousParentSibling = doc.getPreviousSibling(parent.key);
|
||||
if (previousParentSibling && previousParentSibling.type === type) {
|
||||
return editor.mergeNodeByKey(parent.key);
|
||||
}
|
||||
|
||||
return editor.unwrapNodeByKey(startBlock.key);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default QuoteBlock;
|
||||
|
@ -1,14 +1,16 @@
|
||||
import isHotkey from 'is-hotkey';
|
||||
|
||||
const SelectAll = () => ({
|
||||
onKeyDown(event, editor, next) {
|
||||
const isModA = isHotkey('mod+a', event);
|
||||
if (!isModA) {
|
||||
return next();
|
||||
}
|
||||
event.preventDefault();
|
||||
return editor.moveToRangeOfDocument();
|
||||
},
|
||||
});
|
||||
function SelectAll() {
|
||||
return {
|
||||
onKeyDown(event, editor, next) {
|
||||
const isModA = isHotkey('mod+a', event);
|
||||
if (!isModA) {
|
||||
return next();
|
||||
}
|
||||
event.preventDefault();
|
||||
return editor.moveToRangeOfDocument();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default SelectAll;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Text, Block } from 'slate';
|
||||
|
||||
const createShortcodeBlock = shortcodeConfig => {
|
||||
function createShortcodeBlock(shortcodeConfig) {
|
||||
// Handle code block component
|
||||
if (shortcodeConfig.type === 'code-block') {
|
||||
return Block.create({ type: shortcodeConfig.type, data: { shortcodeNew: true } });
|
||||
@ -25,23 +25,25 @@ const createShortcodeBlock = shortcodeConfig => {
|
||||
},
|
||||
nodes,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const Shortcode = ({ defaultType }) => ({
|
||||
commands: {
|
||||
insertShortcode(editor, shortcodeConfig) {
|
||||
const block = createShortcodeBlock(shortcodeConfig);
|
||||
const { focusBlock } = editor.value;
|
||||
function Shortcode({ defaultType }) {
|
||||
return {
|
||||
commands: {
|
||||
insertShortcode(editor, shortcodeConfig) {
|
||||
const block = createShortcodeBlock(shortcodeConfig);
|
||||
const { focusBlock } = editor.value;
|
||||
|
||||
if (focusBlock.text === '' && focusBlock.type === defaultType) {
|
||||
editor.replaceNodeByKey(focusBlock.key, block);
|
||||
} else {
|
||||
editor.insertBlock(block);
|
||||
}
|
||||
if (focusBlock.text === '' && focusBlock.type === defaultType) {
|
||||
editor.replaceNodeByKey(focusBlock.key, block);
|
||||
} else {
|
||||
editor.insertBlock(block);
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
editor.focus();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default Shortcode;
|
||||
|
@ -14,39 +14,45 @@ import Shortcode from './Shortcode';
|
||||
import { SLATE_DEFAULT_BLOCK_TYPE as defaultType } from '../../types';
|
||||
import Hotkey, { HOT_KEY_MAP } from './Hotkey';
|
||||
|
||||
const plugins = ({ getAsset, resolveWidget, t }) => [
|
||||
{
|
||||
onKeyDown(event, editor, next) {
|
||||
if (isHotkey('mod+j', event)) {
|
||||
console.log(JSON.stringify(editor.value.document.toJS(), null, 2));
|
||||
}
|
||||
next();
|
||||
function plugins({ getAsset, resolveWidget, t }) {
|
||||
return [
|
||||
{
|
||||
onKeyDown(event, editor, next) {
|
||||
if (isHotkey('mod+j', event)) {
|
||||
console.log(JSON.stringify(editor.value.document.toJS(), null, 2));
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
},
|
||||
Hotkey(HOT_KEY_MAP['bold'], e => e.toggleMark('bold')),
|
||||
Hotkey(HOT_KEY_MAP['code'], e => e.toggleMark('code')),
|
||||
Hotkey(HOT_KEY_MAP['italic'], e => e.toggleMark('italic')),
|
||||
Hotkey(HOT_KEY_MAP['strikethrough'], e => e.toggleMark('strikethrough')),
|
||||
Hotkey(HOT_KEY_MAP['heading-one'], e => e.toggleBlock('heading-one')),
|
||||
Hotkey(HOT_KEY_MAP['heading-two'], e => e.toggleBlock('heading-two')),
|
||||
Hotkey(HOT_KEY_MAP['heading-three'], e => e.toggleBlock('heading-three')),
|
||||
Hotkey(HOT_KEY_MAP['heading-four'], e => e.toggleBlock('heading-four')),
|
||||
Hotkey(HOT_KEY_MAP['heading-five'], e => e.toggleBlock('heading-five')),
|
||||
Hotkey(HOT_KEY_MAP['heading-six'], e => e.toggleBlock('heading-six')),
|
||||
Hotkey(HOT_KEY_MAP['link'], e =>
|
||||
e.toggleLink(() => window.prompt(t('editor.editorWidgets.markdown.linkPrompt'))),
|
||||
),
|
||||
CommandsAndQueries({ defaultType }),
|
||||
QuoteBlock({ defaultType, type: 'quote' }),
|
||||
ListPlugin({ defaultType, unorderedListType: 'bulleted-list', orderedListType: 'numbered-list' }),
|
||||
Link({ type: 'link' }),
|
||||
LineBreak(),
|
||||
BreakToDefaultBlock({ defaultType }),
|
||||
CloseBlock({ defaultType }),
|
||||
SelectAll(),
|
||||
ForceInsert({ defaultType }),
|
||||
CopyPasteVisual({ getAsset, resolveWidget }),
|
||||
Shortcode({ defaultType }),
|
||||
];
|
||||
Hotkey(HOT_KEY_MAP['bold'], e => e.toggleMark('bold')),
|
||||
Hotkey(HOT_KEY_MAP['code'], e => e.toggleMark('code')),
|
||||
Hotkey(HOT_KEY_MAP['italic'], e => e.toggleMark('italic')),
|
||||
Hotkey(HOT_KEY_MAP['strikethrough'], e => e.toggleMark('strikethrough')),
|
||||
Hotkey(HOT_KEY_MAP['heading-one'], e => e.toggleBlock('heading-one')),
|
||||
Hotkey(HOT_KEY_MAP['heading-two'], e => e.toggleBlock('heading-two')),
|
||||
Hotkey(HOT_KEY_MAP['heading-three'], e => e.toggleBlock('heading-three')),
|
||||
Hotkey(HOT_KEY_MAP['heading-four'], e => e.toggleBlock('heading-four')),
|
||||
Hotkey(HOT_KEY_MAP['heading-five'], e => e.toggleBlock('heading-five')),
|
||||
Hotkey(HOT_KEY_MAP['heading-six'], e => e.toggleBlock('heading-six')),
|
||||
Hotkey(HOT_KEY_MAP['link'], e =>
|
||||
e.toggleLink(() => window.prompt(t('editor.editorWidgets.markdown.linkPrompt'))),
|
||||
),
|
||||
CommandsAndQueries({ defaultType }),
|
||||
QuoteBlock({ defaultType, type: 'quote' }),
|
||||
ListPlugin({
|
||||
defaultType,
|
||||
unorderedListType: 'bulleted-list',
|
||||
orderedListType: 'numbered-list',
|
||||
}),
|
||||
Link({ type: 'link' }),
|
||||
LineBreak(),
|
||||
BreakToDefaultBlock({ defaultType }),
|
||||
CloseBlock({ defaultType }),
|
||||
SelectAll(),
|
||||
ForceInsert({ defaultType }),
|
||||
CopyPasteVisual({ getAsset, resolveWidget }),
|
||||
Shortcode({ defaultType }),
|
||||
];
|
||||
}
|
||||
|
||||
export default plugins;
|
||||
|
@ -123,56 +123,118 @@ const StyledTd = styled.td`
|
||||
/**
|
||||
* Mark Components
|
||||
*/
|
||||
const Bold = props => <strong>{props.children}</strong>;
|
||||
const Italic = props => <em>{props.children}</em>;
|
||||
const Strikethrough = props => <s>{props.children}</s>;
|
||||
const Code = props => <StyledCode>{props.children}</StyledCode>;
|
||||
function Bold(props) {
|
||||
return <strong>{props.children}</strong>;
|
||||
}
|
||||
|
||||
function Italic(props) {
|
||||
return <em>{props.children}</em>;
|
||||
}
|
||||
|
||||
function Strikethrough(props) {
|
||||
return <s>{props.children}</s>;
|
||||
}
|
||||
|
||||
function Code(props) {
|
||||
return <StyledCode>{props.children}</StyledCode>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node Components
|
||||
*/
|
||||
const Paragraph = props => <StyledP {...props.attributes}>{props.children}</StyledP>;
|
||||
const ListItem = props => <StyledLi {...props.attributes}>{props.children}</StyledLi>;
|
||||
const Quote = props => <StyledBlockQuote {...props.attributes}>{props.children}</StyledBlockQuote>;
|
||||
const CodeBlock = props => (
|
||||
<StyledPre>
|
||||
<StyledCode {...props.attributes}>{props.children}</StyledCode>
|
||||
</StyledPre>
|
||||
);
|
||||
const HeadingOne = props => <StyledH1 {...props.attributes}>{props.children}</StyledH1>;
|
||||
const HeadingTwo = props => <StyledH2 {...props.attributes}>{props.children}</StyledH2>;
|
||||
const HeadingThree = props => <StyledH3 {...props.attributes}>{props.children}</StyledH3>;
|
||||
const HeadingFour = props => <StyledH4 {...props.attributes}>{props.children}</StyledH4>;
|
||||
const HeadingFive = props => <StyledH5 {...props.attributes}>{props.children}</StyledH5>;
|
||||
const HeadingSix = props => <StyledH6 {...props.attributes}>{props.children}</StyledH6>;
|
||||
const Table = props => (
|
||||
<StyledTable>
|
||||
<tbody {...props.attributes}>{props.children}</tbody>
|
||||
</StyledTable>
|
||||
);
|
||||
const TableRow = props => <tr {...props.attributes}>{props.children}</tr>;
|
||||
const TableCell = props => <StyledTd {...props.attributes}>{props.children}</StyledTd>;
|
||||
const ThematicBreak = props => (
|
||||
<StyledHr
|
||||
{...props.attributes}
|
||||
css={
|
||||
props.editor.isSelected(props.node) &&
|
||||
css`
|
||||
box-shadow: 0 0 0 2px ${colors.active};
|
||||
border-radius: 8px;
|
||||
color: ${colors.active};
|
||||
`
|
||||
}
|
||||
/>
|
||||
);
|
||||
const Break = props => <br {...props.attributes} />;
|
||||
const BulletedList = props => <StyledUl {...props.attributes}>{props.children}</StyledUl>;
|
||||
const NumberedList = props => (
|
||||
<StyledOl {...props.attributes} start={props.node.data.get('start') || 1}>
|
||||
{props.children}
|
||||
</StyledOl>
|
||||
);
|
||||
const Link = props => {
|
||||
function Paragraph(props) {
|
||||
return <StyledP {...props.attributes}>{props.children}</StyledP>;
|
||||
}
|
||||
|
||||
function ListItem(props) {
|
||||
return <StyledLi {...props.attributes}>{props.children}</StyledLi>;
|
||||
}
|
||||
|
||||
function Quote(props) {
|
||||
return <StyledBlockQuote {...props.attributes}>{props.children}</StyledBlockQuote>;
|
||||
}
|
||||
|
||||
function CodeBlock(props) {
|
||||
return (
|
||||
<StyledPre>
|
||||
<StyledCode {...props.attributes}>{props.children}</StyledCode>
|
||||
</StyledPre>
|
||||
);
|
||||
}
|
||||
|
||||
function HeadingOne(props) {
|
||||
return <StyledH1 {...props.attributes}>{props.children}</StyledH1>;
|
||||
}
|
||||
|
||||
function HeadingTwo(props) {
|
||||
return <StyledH2 {...props.attributes}>{props.children}</StyledH2>;
|
||||
}
|
||||
|
||||
function HeadingThree(props) {
|
||||
return <StyledH3 {...props.attributes}>{props.children}</StyledH3>;
|
||||
}
|
||||
|
||||
function HeadingFour(props) {
|
||||
return <StyledH4 {...props.attributes}>{props.children}</StyledH4>;
|
||||
}
|
||||
|
||||
function HeadingFive(props) {
|
||||
return <StyledH5 {...props.attributes}>{props.children}</StyledH5>;
|
||||
}
|
||||
|
||||
function HeadingSix(props) {
|
||||
return <StyledH6 {...props.attributes}>{props.children}</StyledH6>;
|
||||
}
|
||||
|
||||
function Table(props) {
|
||||
return (
|
||||
<StyledTable>
|
||||
<tbody {...props.attributes}>{props.children}</tbody>
|
||||
</StyledTable>
|
||||
);
|
||||
}
|
||||
|
||||
function TableRow(props) {
|
||||
return <tr {...props.attributes}>{props.children}</tr>;
|
||||
}
|
||||
|
||||
function TableCell(props) {
|
||||
return <StyledTd {...props.attributes}>{props.children}</StyledTd>;
|
||||
}
|
||||
|
||||
function ThematicBreak(props) {
|
||||
return (
|
||||
<StyledHr
|
||||
{...props.attributes}
|
||||
css={
|
||||
props.editor.isSelected(props.node) &&
|
||||
css`
|
||||
box-shadow: 0 0 0 2px ${colors.active};
|
||||
border-radius: 8px;
|
||||
color: ${colors.active};
|
||||
`
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Break(props) {
|
||||
return <br {...props.attributes} />;
|
||||
}
|
||||
|
||||
function BulletedList(props) {
|
||||
return <StyledUl {...props.attributes}>{props.children}</StyledUl>;
|
||||
}
|
||||
|
||||
function NumberedList(props) {
|
||||
return (
|
||||
<StyledOl {...props.attributes} start={props.node.data.get('start') || 1}>
|
||||
{props.children}
|
||||
</StyledOl>
|
||||
);
|
||||
}
|
||||
|
||||
function Link(props) {
|
||||
const data = props.node.get('data');
|
||||
const url = data.get('url');
|
||||
const title = data.get('title');
|
||||
@ -181,9 +243,9 @@ const Link = props => {
|
||||
{props.children}
|
||||
</StyledA>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const Image = props => {
|
||||
function Image(props) {
|
||||
const data = props.node.get('data');
|
||||
const marks = data.get('marks');
|
||||
const url = data.get('url');
|
||||
@ -196,87 +258,93 @@ const Image = props => {
|
||||
return renderMark({ mark, children: acc });
|
||||
}, image);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export const renderMark = () => props => {
|
||||
switch (props.mark.type) {
|
||||
case 'bold':
|
||||
return <Bold {...props} />;
|
||||
case 'italic':
|
||||
return <Italic {...props} />;
|
||||
case 'strikethrough':
|
||||
return <Strikethrough {...props} />;
|
||||
case 'code':
|
||||
return <Code {...props} />;
|
||||
}
|
||||
};
|
||||
export function renderMark() {
|
||||
return props => {
|
||||
switch (props.mark.type) {
|
||||
case 'bold':
|
||||
return <Bold {...props} />;
|
||||
case 'italic':
|
||||
return <Italic {...props} />;
|
||||
case 'strikethrough':
|
||||
return <Strikethrough {...props} />;
|
||||
case 'code':
|
||||
return <Code {...props} />;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const renderInline = () => props => {
|
||||
switch (props.node.type) {
|
||||
case 'link':
|
||||
return <Link {...props} />;
|
||||
case 'image':
|
||||
return <Image {...props} />;
|
||||
case 'break':
|
||||
return <Break {...props} />;
|
||||
}
|
||||
};
|
||||
export function renderInline() {
|
||||
return props => {
|
||||
switch (props.node.type) {
|
||||
case 'link':
|
||||
return <Link {...props} />;
|
||||
case 'image':
|
||||
return <Image {...props} />;
|
||||
case 'break':
|
||||
return <Break {...props} />;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const renderBlock = ({ classNameWrapper, codeBlockComponent }) => props => {
|
||||
switch (props.node.type) {
|
||||
case 'paragraph':
|
||||
return <Paragraph {...props} />;
|
||||
case 'list-item':
|
||||
return <ListItem {...props} />;
|
||||
case 'quote':
|
||||
return <Quote {...props} />;
|
||||
case 'code-block':
|
||||
if (codeBlockComponent) {
|
||||
export function renderBlock({ classNameWrapper, codeBlockComponent }) {
|
||||
return props => {
|
||||
switch (props.node.type) {
|
||||
case 'paragraph':
|
||||
return <Paragraph {...props} />;
|
||||
case 'list-item':
|
||||
return <ListItem {...props} />;
|
||||
case 'quote':
|
||||
return <Quote {...props} />;
|
||||
case 'code-block':
|
||||
if (codeBlockComponent) {
|
||||
return (
|
||||
<VoidBlock {...props}>
|
||||
<Shortcode
|
||||
classNameWrapper={classNameWrapper}
|
||||
typeOverload="code-block"
|
||||
dataKey={false}
|
||||
{...props}
|
||||
/>
|
||||
</VoidBlock>
|
||||
);
|
||||
}
|
||||
return <CodeBlock {...props} />;
|
||||
case 'heading-one':
|
||||
return <HeadingOne {...props} />;
|
||||
case 'heading-two':
|
||||
return <HeadingTwo {...props} />;
|
||||
case 'heading-three':
|
||||
return <HeadingThree {...props} />;
|
||||
case 'heading-four':
|
||||
return <HeadingFour {...props} />;
|
||||
case 'heading-five':
|
||||
return <HeadingFive {...props} />;
|
||||
case 'heading-six':
|
||||
return <HeadingSix {...props} />;
|
||||
case 'table':
|
||||
return <Table {...props} />;
|
||||
case 'table-row':
|
||||
return <TableRow {...props} />;
|
||||
case 'table-cell':
|
||||
return <TableCell {...props} />;
|
||||
case 'thematic-break':
|
||||
return (
|
||||
<VoidBlock {...props}>
|
||||
<Shortcode
|
||||
classNameWrapper={classNameWrapper}
|
||||
typeOverload="code-block"
|
||||
dataKey={false}
|
||||
{...props}
|
||||
/>
|
||||
<ThematicBreak editor={props.editor} node={props.node} />
|
||||
</VoidBlock>
|
||||
);
|
||||
}
|
||||
return <CodeBlock {...props} />;
|
||||
case 'heading-one':
|
||||
return <HeadingOne {...props} />;
|
||||
case 'heading-two':
|
||||
return <HeadingTwo {...props} />;
|
||||
case 'heading-three':
|
||||
return <HeadingThree {...props} />;
|
||||
case 'heading-four':
|
||||
return <HeadingFour {...props} />;
|
||||
case 'heading-five':
|
||||
return <HeadingFive {...props} />;
|
||||
case 'heading-six':
|
||||
return <HeadingSix {...props} />;
|
||||
case 'table':
|
||||
return <Table {...props} />;
|
||||
case 'table-row':
|
||||
return <TableRow {...props} />;
|
||||
case 'table-cell':
|
||||
return <TableCell {...props} />;
|
||||
case 'thematic-break':
|
||||
return (
|
||||
<VoidBlock {...props}>
|
||||
<ThematicBreak editor={props.editor} node={props.node} />
|
||||
</VoidBlock>
|
||||
);
|
||||
case 'bulleted-list':
|
||||
return <BulletedList {...props} />;
|
||||
case 'numbered-list':
|
||||
return <NumberedList {...props} />;
|
||||
case 'shortcode':
|
||||
return (
|
||||
<VoidBlock {...props}>
|
||||
<Shortcode classNameWrapper={classNameWrapper} {...props} />
|
||||
</VoidBlock>
|
||||
);
|
||||
}
|
||||
};
|
||||
case 'bulleted-list':
|
||||
return <BulletedList {...props} />;
|
||||
case 'numbered-list':
|
||||
return <NumberedList {...props} />;
|
||||
case 'shortcode':
|
||||
return (
|
||||
<VoidBlock {...props}>
|
||||
<Shortcode classNameWrapper={classNameWrapper} {...props} />
|
||||
</VoidBlock>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -26,247 +26,249 @@ const codeBlockOverride = {
|
||||
isVoid: true,
|
||||
};
|
||||
|
||||
const schema = ({ voidCodeBlock } = {}) => ({
|
||||
rules: [
|
||||
/**
|
||||
* Document
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'document' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If no blocks present, insert one.
|
||||
case 'child_min_invalid': {
|
||||
const node = { object: 'block', type: 'paragraph' };
|
||||
editor.insertNodeByKey(error.node.key, 0, node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Block Containers
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'block', type: 'quote' },
|
||||
{ object: 'block', type: 'list-item' },
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
match: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* List Items
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'list-item' }],
|
||||
parent: [{ type: 'bulleted-list' }, { type: 'numbered-list' }],
|
||||
},
|
||||
|
||||
/**
|
||||
* Blocks
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'block', type: 'paragraph' },
|
||||
{ object: 'block', type: 'heading-one' },
|
||||
{ object: 'block', type: 'heading-two' },
|
||||
{ object: 'block', type: 'heading-three' },
|
||||
{ object: 'block', type: 'heading-four' },
|
||||
{ object: 'block', type: 'heading-five' },
|
||||
{ object: 'block', type: 'heading-six' },
|
||||
{ object: 'block', type: 'table-cell' },
|
||||
{ object: 'inline', type: 'link' },
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'text' }, { type: 'link' }, { type: 'image' }, { type: 'break' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulleted List
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'bulleted-list' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ type: 'list-item' }],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If a list has no list items, remove the list
|
||||
case 'child_min_invalid':
|
||||
editor.removeNodeByKey(error.node.key);
|
||||
return;
|
||||
|
||||
// If two bulleted lists are immediately adjacent, join them
|
||||
case 'next_sibling_type_invalid':
|
||||
if (error.next.type === 'bulleted-list') {
|
||||
editor.mergeNodeByKey(error.next.key);
|
||||
function schema({ voidCodeBlock } = {}) {
|
||||
return {
|
||||
rules: [
|
||||
/**
|
||||
* Document
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'document' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If no blocks present, insert one.
|
||||
case 'child_min_invalid': {
|
||||
const node = { object: 'block', type: 'paragraph' };
|
||||
editor.insertNodeByKey(error.node.key, 0, node);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Numbered List
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'numbered-list' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ type: 'list-item' }],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If a list has no list items, remove the list
|
||||
case 'child_min_invalid':
|
||||
editor.removeNodeByKey(error.node.key);
|
||||
return;
|
||||
|
||||
// If two numbered lists are immediately adjacent, join them
|
||||
case 'next_sibling_type_invalid': {
|
||||
if (error.next.type === 'numbered-list') {
|
||||
editor.mergeNodeByKey(error.next.key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Voids
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'inline', type: 'image' },
|
||||
{ object: 'inline', type: 'break' },
|
||||
{ object: 'block', type: 'thematic-break' },
|
||||
{ object: 'block', type: 'shortcode' },
|
||||
],
|
||||
isVoid: true,
|
||||
},
|
||||
/**
|
||||
* Block Containers
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'block', type: 'quote' },
|
||||
{ object: 'block', type: 'list-item' },
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
match: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Table
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'table' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-row' }],
|
||||
/**
|
||||
* List Items
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'list-item' }],
|
||||
parent: [{ type: 'bulleted-list' }, { type: 'numbered-list' }],
|
||||
},
|
||||
|
||||
/**
|
||||
* Blocks
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'block', type: 'paragraph' },
|
||||
{ object: 'block', type: 'heading-one' },
|
||||
{ object: 'block', type: 'heading-two' },
|
||||
{ object: 'block', type: 'heading-three' },
|
||||
{ object: 'block', type: 'heading-four' },
|
||||
{ object: 'block', type: 'heading-five' },
|
||||
{ object: 'block', type: 'heading-six' },
|
||||
{ object: 'block', type: 'table-cell' },
|
||||
{ object: 'inline', type: 'link' },
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'text' }, { type: 'link' }, { type: 'image' }, { type: 'break' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulleted List
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'bulleted-list' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ type: 'list-item' }],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'numbered-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If a list has no list items, remove the list
|
||||
case 'child_min_invalid':
|
||||
editor.removeNodeByKey(error.node.key);
|
||||
return;
|
||||
|
||||
// If two bulleted lists are immediately adjacent, join them
|
||||
case 'next_sibling_type_invalid':
|
||||
if (error.next.type === 'bulleted-list') {
|
||||
editor.mergeNodeByKey(error.next.key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Table Row
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-row' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-cell' }],
|
||||
/**
|
||||
* Numbered List
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'numbered-list' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ type: 'list-item' }],
|
||||
min: 1,
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{ type: 'paragraph' },
|
||||
{ type: 'heading-one' },
|
||||
{ type: 'heading-two' },
|
||||
{ type: 'heading-three' },
|
||||
{ type: 'heading-four' },
|
||||
{ type: 'heading-five' },
|
||||
{ type: 'heading-six' },
|
||||
{ type: 'quote' },
|
||||
{ type: 'code-block' },
|
||||
{ type: 'bulleted-list' },
|
||||
{ type: 'thematic-break' },
|
||||
{ type: 'table' },
|
||||
{ type: 'shortcode' },
|
||||
],
|
||||
normalize: (editor, error) => {
|
||||
switch (error.code) {
|
||||
// If a list has no list items, remove the list
|
||||
case 'child_min_invalid':
|
||||
editor.removeNodeByKey(error.node.key);
|
||||
return;
|
||||
|
||||
// If two numbered lists are immediately adjacent, join them
|
||||
case 'next_sibling_type_invalid': {
|
||||
if (error.next.type === 'numbered-list') {
|
||||
editor.mergeNodeByKey(error.next.key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'mark', type: 'bold' },
|
||||
{ object: 'mark', type: 'italic' },
|
||||
{ object: 'mark', type: 'strikethrough' },
|
||||
{ object: 'mark', type: 'code' },
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Voids
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'inline', type: 'image' },
|
||||
{ object: 'inline', type: 'break' },
|
||||
{ object: 'block', type: 'thematic-break' },
|
||||
{ object: 'block', type: 'shortcode' },
|
||||
],
|
||||
isVoid: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides
|
||||
*/
|
||||
voidCodeBlock ? codeBlockOverride : codeBlock,
|
||||
],
|
||||
});
|
||||
/**
|
||||
* Table
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'table' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-row' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Table Row
|
||||
*/
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-row' }],
|
||||
nodes: [
|
||||
{
|
||||
match: [{ object: 'block', type: 'table-cell' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks
|
||||
*/
|
||||
{
|
||||
match: [
|
||||
{ object: 'mark', type: 'bold' },
|
||||
{ object: 'mark', type: 'italic' },
|
||||
{ object: 'mark', type: 'strikethrough' },
|
||||
{ object: 'mark', type: 'code' },
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides
|
||||
*/
|
||||
voidCodeBlock ? codeBlockOverride : codeBlock,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export default schema;
|
||||
|
@ -2,13 +2,15 @@ import controlComponent from './MarkdownControl';
|
||||
import previewComponent from './MarkdownPreview';
|
||||
import schema from './schema';
|
||||
|
||||
const Widget = (opts = {}) => ({
|
||||
name: 'markdown',
|
||||
controlComponent,
|
||||
previewComponent,
|
||||
schema,
|
||||
...opts,
|
||||
});
|
||||
function Widget(opts = {}) {
|
||||
return {
|
||||
name: 'markdown',
|
||||
controlComponent,
|
||||
previewComponent,
|
||||
schema,
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
export const NetlifyCmsWidgetMarkdown = { Widget, controlComponent, previewComponent };
|
||||
export default NetlifyCmsWidgetMarkdown;
|
||||
|
@ -68,10 +68,12 @@ const onlys = [
|
||||
*/
|
||||
const reader = new commonmark.Parser();
|
||||
const writer = new commonmark.HtmlRenderer();
|
||||
const parseWithCommonmark = markdown => {
|
||||
|
||||
function parseWithCommonmark(markdown) {
|
||||
const parsed = reader.parse(markdown);
|
||||
return writer.render(parsed);
|
||||
};
|
||||
}
|
||||
|
||||
const parse = flow([markdownToSlate, slateToMarkdown]);
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,7 @@ import unified from 'unified';
|
||||
import markdownToRemark from 'remark-parse';
|
||||
import remarkAllowHtmlEntities from '../remarkAllowHtmlEntities';
|
||||
|
||||
const process = markdown => {
|
||||
function process(markdown) {
|
||||
const mdast = unified()
|
||||
.use(markdownToRemark)
|
||||
.use(remarkAllowHtmlEntities)
|
||||
@ -18,7 +18,7 @@ const process = markdown => {
|
||||
* ]}
|
||||
*/
|
||||
return mdast.children[0].children[0].value;
|
||||
};
|
||||
}
|
||||
|
||||
describe('remarkAllowHtmlEntities', () => {
|
||||
it('should not decode HTML entities', () => {
|
||||
|
@ -2,14 +2,14 @@ import unified from 'unified';
|
||||
import u from 'unist-builder';
|
||||
import remarkEscapeMarkdownEntities from '../remarkEscapeMarkdownEntities';
|
||||
|
||||
const process = text => {
|
||||
function process(text) {
|
||||
const tree = u('root', [u('text', text)]);
|
||||
const escapedMdast = unified()
|
||||
.use(remarkEscapeMarkdownEntities)
|
||||
.runSync(tree);
|
||||
|
||||
return escapedMdast.children[0].value;
|
||||
};
|
||||
}
|
||||
|
||||
describe('remarkEscapeMarkdownEntities', () => {
|
||||
it('should escape common markdown entities', () => {
|
||||
|
@ -3,18 +3,20 @@ import markdownToRemark from 'remark-parse';
|
||||
import remarkToMarkdown from 'remark-stringify';
|
||||
import remarkPaddedLinks from '../remarkPaddedLinks';
|
||||
|
||||
const input = markdown =>
|
||||
unified()
|
||||
function input(markdown) {
|
||||
return unified()
|
||||
.use(markdownToRemark)
|
||||
.use(remarkPaddedLinks)
|
||||
.use(remarkToMarkdown)
|
||||
.processSync(markdown).contents;
|
||||
}
|
||||
|
||||
const output = markdown =>
|
||||
unified()
|
||||
function output(markdown) {
|
||||
return unified()
|
||||
.use(markdownToRemark)
|
||||
.use(remarkToMarkdown)
|
||||
.processSync(markdown).contents;
|
||||
}
|
||||
|
||||
describe('remarkPaddedLinks', () => {
|
||||
it('should move leading and trailing spaces outside of a link', () => {
|
||||
|
@ -2,7 +2,10 @@ import { remarkParseShortcodes } from '../remarkShortcodes';
|
||||
|
||||
// Stub of Remark Parser
|
||||
function process(value, plugins, processEat = () => {}) {
|
||||
const eat = () => processEat;
|
||||
function eat() {
|
||||
return processEat;
|
||||
}
|
||||
|
||||
function Parser() {}
|
||||
Parser.prototype.blockTokenizers = {};
|
||||
Parser.prototype.blockMethods = [];
|
||||
|
@ -2,14 +2,14 @@ import unified from 'unified';
|
||||
import u from 'unist-builder';
|
||||
import remarkStripTrailingBreaks from '../remarkStripTrailingBreaks';
|
||||
|
||||
const process = children => {
|
||||
function process(children) {
|
||||
const tree = u('root', children);
|
||||
const strippedMdast = unified()
|
||||
.use(remarkStripTrailingBreaks)
|
||||
.runSync(tree);
|
||||
|
||||
return strippedMdast.children;
|
||||
};
|
||||
}
|
||||
|
||||
describe('remarkStripTrailingBreaks', () => {
|
||||
it('should remove trailing breaks at the end of a block', () => {
|
||||
|
@ -58,7 +58,7 @@ import { getEditorComponents } from '../MarkdownControl';
|
||||
/**
|
||||
* Deserialize a Markdown string to an MDAST.
|
||||
*/
|
||||
export const markdownToRemark = markdown => {
|
||||
export function markdownToRemark(markdown) {
|
||||
/**
|
||||
* Parse the Markdown string input to an MDAST.
|
||||
*/
|
||||
@ -77,7 +77,7 @@ export const markdownToRemark = markdown => {
|
||||
.runSync(parsed);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove named tokenizers from the parser, effectively deactivating them.
|
||||
@ -92,7 +92,7 @@ function markdownToRemarkRemoveTokenizers({ inlineTokenizers }) {
|
||||
/**
|
||||
* Serialize an MDAST to a Markdown string.
|
||||
*/
|
||||
export const remarkToMarkdown = obj => {
|
||||
export function remarkToMarkdown(obj) {
|
||||
/**
|
||||
* Rewrite the remark-stringify text visitor to simply return the text value,
|
||||
* without encoding or escaping any characters. This means we're completely
|
||||
@ -143,12 +143,12 @@ export const remarkToMarkdown = obj => {
|
||||
* Return markdown with trailing whitespace removed.
|
||||
*/
|
||||
return trimEnd(markdown);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Markdown to HTML.
|
||||
*/
|
||||
export const markdownToHtml = (markdown, { getAsset, resolveWidget } = {}) => {
|
||||
export function markdownToHtml(markdown, { getAsset, resolveWidget } = {}) {
|
||||
const mdast = markdownToRemark(markdown);
|
||||
|
||||
const hast = unified()
|
||||
@ -166,13 +166,13 @@ export const markdownToHtml = (markdown, { getAsset, resolveWidget } = {}) => {
|
||||
.stringify(hast);
|
||||
|
||||
return html;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an HTML string to Slate's Raw AST. Currently used for HTML
|
||||
* pastes.
|
||||
*/
|
||||
export const htmlToSlate = html => {
|
||||
export function htmlToSlate(html) {
|
||||
const hast = unified()
|
||||
.use(htmlToRehype, { fragment: true })
|
||||
.parse(html);
|
||||
@ -190,12 +190,12 @@ export const htmlToSlate = html => {
|
||||
.runSync(mdast);
|
||||
|
||||
return slateRaw;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Markdown to Slate's Raw AST.
|
||||
*/
|
||||
export const markdownToSlate = (markdown, { voidCodeBlock } = {}) => {
|
||||
export function markdownToSlate(markdown, { voidCodeBlock } = {}) {
|
||||
const mdast = markdownToRemark(markdown);
|
||||
|
||||
const slateRaw = unified()
|
||||
@ -204,7 +204,7 @@ export const markdownToSlate = (markdown, { voidCodeBlock } = {}) => {
|
||||
.runSync(mdast);
|
||||
|
||||
return slateRaw;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Slate Raw AST to Markdown.
|
||||
@ -215,8 +215,8 @@ export const markdownToSlate = (markdown, { voidCodeBlock } = {}) => {
|
||||
* MDAST. The conversion is manual because Unified can only operate on Unist
|
||||
* trees.
|
||||
*/
|
||||
export const slateToMarkdown = (raw, { voidCodeBlock } = {}) => {
|
||||
export function slateToMarkdown(raw, { voidCodeBlock } = {}) {
|
||||
const mdast = slateToRemark(raw, { voidCodeBlock });
|
||||
const markdown = remarkToMarkdown(mdast);
|
||||
return markdown;
|
||||
};
|
||||
}
|
||||
|
@ -4,12 +4,13 @@
|
||||
* replaces the images with the emoji characters.
|
||||
*/
|
||||
export default function rehypePaperEmoji() {
|
||||
const transform = node => {
|
||||
function transform(node) {
|
||||
if (node.tagName === 'img' && node.properties.dataEmojiCh) {
|
||||
return { type: 'text', value: node.properties.dataEmojiCh };
|
||||
}
|
||||
node.children = node.children ? node.children.map(transform) : node.children;
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ function escape(delim) {
|
||||
* stringification.
|
||||
*/
|
||||
export default function remarkEscapeMarkdownEntities() {
|
||||
const transform = (node, index) => {
|
||||
function transform(node, index) {
|
||||
/**
|
||||
* Shortcode nodes will intentionally inject markdown entities in text node
|
||||
* children not be escaped.
|
||||
@ -262,7 +262,7 @@ export default function remarkEscapeMarkdownEntities() {
|
||||
* Always return nodes with recursively mapped children.
|
||||
*/
|
||||
return { ...node, ...children };
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
@ -28,11 +28,19 @@ const markMap = {
|
||||
inlineCode: 'code',
|
||||
};
|
||||
|
||||
const isInline = node => node.object === 'inline';
|
||||
const isText = node => node.object === 'text';
|
||||
const isMarksEqual = (node1, node2) => isEqual(node1.marks, node2.marks);
|
||||
function isInline(node) {
|
||||
return node.object === 'inline';
|
||||
}
|
||||
|
||||
export const wrapInlinesWithTexts = children => {
|
||||
function isText(node) {
|
||||
return node.object === 'text';
|
||||
}
|
||||
|
||||
function isMarksEqual(node1, node2) {
|
||||
return isEqual(node1.marks, node2.marks);
|
||||
}
|
||||
|
||||
export function wrapInlinesWithTexts(children) {
|
||||
if (children.length <= 0) {
|
||||
return children;
|
||||
}
|
||||
@ -63,9 +71,9 @@ export const wrapInlinesWithTexts = children => {
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
}
|
||||
|
||||
export const mergeAdjacentTexts = children => {
|
||||
export function mergeAdjacentTexts(children) {
|
||||
if (children.length <= 0) {
|
||||
return children;
|
||||
}
|
||||
@ -96,7 +104,7 @@ export const mergeAdjacentTexts = children => {
|
||||
}
|
||||
|
||||
return mergedChildren;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A Remark plugin for converting an MDAST to Slate Raw AST. Remark plugins
|
||||
|
@ -10,7 +10,7 @@ import mdastToString from 'mdast-util-to-string';
|
||||
* these artifacts in resulting markdown.
|
||||
*/
|
||||
export default function remarkStripTrailingBreaks() {
|
||||
const transform = node => {
|
||||
function transform(node) {
|
||||
if (node.children) {
|
||||
node.children = node.children
|
||||
.map((child, idx, children) => {
|
||||
@ -50,6 +50,7 @@ export default function remarkStripTrailingBreaks() {
|
||||
.map(transform);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
@ -208,6 +208,10 @@ export default function slateToRemark(raw, { voidCodeBlock }) {
|
||||
return { leadingWhitespace, centerNodes, trailingWhitespace };
|
||||
}
|
||||
|
||||
function createText(text) {
|
||||
return text && u('html', text);
|
||||
}
|
||||
|
||||
function convertInlineAndTextChildren(nodes = []) {
|
||||
const convertedNodes = [];
|
||||
let remainingNodes = nodes;
|
||||
@ -243,7 +247,7 @@ export default function slateToRemark(raw, { voidCodeBlock }) {
|
||||
remainingNodes = remainder;
|
||||
continue;
|
||||
}
|
||||
const createText = text => text && u('html', text);
|
||||
|
||||
const normalizedNodes = [
|
||||
createText(leadingWhitespace),
|
||||
markNode,
|
||||
|
Reference in New Issue
Block a user