WIP updates to RichText
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Transforms, Text } from 'slate';
|
||||
import { useSlate } from 'slate-react';
|
||||
import {
|
||||
toggleMark, toggleList, isMarkActive, isNodeInSelection, getNode,
|
||||
} from '@udecode/slate-plugins';
|
||||
|
||||
import { nodeTypes } from '../types';
|
||||
import Button from '../../../../elements/Button';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'command-button';
|
||||
|
||||
const CommandButton = (props) => {
|
||||
const {
|
||||
mark, element, listElement, children, clearStyles,
|
||||
} = props;
|
||||
const editor = useSlate();
|
||||
|
||||
const onClick = () => {
|
||||
if (mark) toggleMark(editor, mark);
|
||||
if (element) editor.toggleType(element);
|
||||
if (listElement) toggleList(editor, { typeList: listElement });
|
||||
if (clearStyles) {
|
||||
const nodeTypesArray = Object.keys(nodeTypes).reduce((acc, key) => [...acc, nodeTypes[key]], []);
|
||||
|
||||
const node = Transforms.collapse(editor);
|
||||
console.log(node);
|
||||
}
|
||||
};
|
||||
|
||||
const isActive = (mark && isMarkActive(editor, mark)) || (element && isNodeInSelection(editor, element));
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
isActive && `${baseClass}--is-active`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classes}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
CommandButton.defaultProps = {
|
||||
mark: null,
|
||||
element: null,
|
||||
listElement: null,
|
||||
clearStyles: false,
|
||||
};
|
||||
|
||||
CommandButton.propTypes = {
|
||||
mark: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
listElement: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
clearStyles: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default CommandButton;
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../../../../scss/styles.scss';
|
||||
@import '../../../../../scss/styles';
|
||||
|
||||
.command-button {
|
||||
background-color: transparent;
|
||||
@@ -7,15 +7,21 @@
|
||||
margin-right: base(.25);
|
||||
padding: base(.25) base(.35);
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&--is-active {
|
||||
background-color: $color-dark-gray;
|
||||
color: white;
|
||||
@include color-svg(white);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Transforms } from 'slate';
|
||||
import { useSlate, useFocused } from 'slate-react';
|
||||
import {
|
||||
StrikethroughIcon, BlockquoteIcon, OrderedListIcon, UnorderedListIcon, ItalicIcon, UnderlineIcon, CodeIcon,
|
||||
} from '../icons';
|
||||
|
||||
import { nodeTypes, headingTypes } from '../types';
|
||||
import CommandButton from '../CommandButton';
|
||||
import ReactSelect from '../../../../elements/ReactSelect';
|
||||
|
||||
import './index.scss';
|
||||
import BoldIcon from '../icons/Bold';
|
||||
|
||||
const baseClass = 'command-toolbar';
|
||||
|
||||
const enabledHeadings = [{ label: 'Normal Text', value: 'p' }];
|
||||
|
||||
const CommandToolbar = (props) => {
|
||||
const { enabledPluginList, maxHeadingLevel, disabledMarks } = props;
|
||||
const editor = useSlate();
|
||||
const editorFocus = useFocused();
|
||||
const [editorFocusedSelection, setEditorFocusedSelection] = useState(editorFocus);
|
||||
const headingsAreEnabled = enabledPluginList.heading !== undefined;
|
||||
const blockquoteIsEnabled = enabledPluginList.blockquote !== undefined;
|
||||
|
||||
const onHeadingSelectChange = (headingType) => {
|
||||
if (editorFocusedSelection) {
|
||||
Transforms.select(editor, editorFocusedSelection);
|
||||
editor.toggleType(headingType);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (editorFocus && editor.selection) setEditorFocusedSelection(editor.selection);
|
||||
}, [editorFocus, editorFocusedSelection, editor.selection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (headingsAreEnabled) {
|
||||
Array.from(Array(maxHeadingLevel)).forEach((_, index) => {
|
||||
enabledHeadings.push({
|
||||
label: `Heading ${index + 1}`,
|
||||
value: headingTypes[index],
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [headingsAreEnabled, maxHeadingLevel]);
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
|
||||
<div className={`${baseClass}__toggles`}>
|
||||
{headingsAreEnabled && (
|
||||
<ReactSelect
|
||||
options={enabledHeadings}
|
||||
onChange={onHeadingSelectChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{blockquoteIsEnabled && (
|
||||
<CommandButton element={nodeTypes.typeBlockquote}>
|
||||
<BlockquoteIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeOl) && (
|
||||
<CommandButton listElement={nodeTypes.typeOl}>
|
||||
<OrderedListIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeUl) && (
|
||||
<CommandButton listElement={nodeTypes.typeUl}>
|
||||
<UnorderedListIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeBold) && (
|
||||
<CommandButton mark={nodeTypes.typeBold}>
|
||||
<BoldIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeItalic) && (
|
||||
<CommandButton mark={nodeTypes.typeItalic}>
|
||||
<ItalicIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeUnderline) && (
|
||||
<CommandButton mark={nodeTypes.typeUnderline}>
|
||||
<UnderlineIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeStrikethrough) && (
|
||||
<CommandButton mark={nodeTypes.typeStrikethrough}>
|
||||
<StrikethroughIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
{!disabledMarks.includes(nodeTypes.typeCode) && (
|
||||
<CommandButton mark={nodeTypes.typeCode}>
|
||||
<CodeIcon />
|
||||
</CommandButton>
|
||||
)}
|
||||
|
||||
<CommandButton clearStyles>X</CommandButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CommandToolbar.defaultProps = {
|
||||
maxHeadingLevel: 6,
|
||||
enabledPluginList: {},
|
||||
disabledMarks: [],
|
||||
};
|
||||
|
||||
CommandToolbar.propTypes = {
|
||||
maxHeadingLevel: PropTypes.number,
|
||||
enabledPluginList: PropTypes.shape({
|
||||
heading: PropTypes.string,
|
||||
blockquote: PropTypes.string,
|
||||
}),
|
||||
disabledMarks: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export default CommandToolbar;
|
||||
@@ -0,0 +1,22 @@
|
||||
@import '../../../../../scss/styles';
|
||||
|
||||
.command-toolbar {
|
||||
border-bottom: 1px solid $color-light-gray;
|
||||
padding: base(.5);
|
||||
background: $color-background-gray;
|
||||
|
||||
&__marks,
|
||||
&__blocks,
|
||||
.react-select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.react-select {
|
||||
margin-right: base(.25);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid $color-light-gray;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Button from '../../../../../elements/Button';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'command-button';
|
||||
|
||||
const CommandButton = (props) => {
|
||||
const { isActive, children } = props;
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
isActive && `${baseClass}--is-active`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classes}
|
||||
size="small"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
CommandButton.defaultProps = {
|
||||
isActive: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
CommandButton.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default CommandButton;
|
||||
@@ -1,21 +0,0 @@
|
||||
@import '../../../../../../scss/styles.scss';
|
||||
|
||||
.command-button {
|
||||
background-color: transparent;
|
||||
color: $color-dark-gray;
|
||||
margin: 0;
|
||||
margin-right: base(.25);
|
||||
padding: base(.25) base(.35);
|
||||
line-height: 1;
|
||||
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&--is-active {
|
||||
background-color: $color-dark-gray;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useSlate } from 'slate-react';
|
||||
|
||||
import RichTextButton from './CommandButton';
|
||||
import { editorCheck, editorCommands } from '../utils';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'commands-toolbar';
|
||||
|
||||
const CommandsToolbar = (props) => {
|
||||
const { className } = props;
|
||||
const editor = useSlate();
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
className && className,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH1Active(editor)}
|
||||
onClick={() => editorCommands.toggleH1Block(editor)}
|
||||
>
|
||||
H1
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH2Active(editor)}
|
||||
onClick={() => editorCommands.toggleH2Block(editor)}
|
||||
>
|
||||
H2
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH3Active(editor)}
|
||||
onClick={() => editorCommands.toggleH3Block(editor)}
|
||||
>
|
||||
H3
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH4Active(editor)}
|
||||
onClick={() => editorCommands.toggleH4Block(editor)}
|
||||
>
|
||||
H4
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH5Active(editor)}
|
||||
onClick={() => editorCommands.toggleH5Block(editor)}
|
||||
>
|
||||
H5
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH6Active(editor)}
|
||||
onClick={() => editorCommands.toggleH6Block(editor)}
|
||||
>
|
||||
H6
|
||||
</RichTextButton>
|
||||
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isBoldActive(editor)}
|
||||
onClick={() => editorCommands.toggleBoldMark(editor)}
|
||||
>
|
||||
Bold
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isItalicActive(editor)}
|
||||
onClick={() => editorCommands.toggleItalicMark(editor)}
|
||||
>
|
||||
Italic
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isUnderlineActive(editor)}
|
||||
onClick={() => editorCommands.toggleUnderlineMark(editor)}
|
||||
>
|
||||
Underline
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isStrikethroughActive(editor)}
|
||||
onClick={() => editorCommands.toggleStrikethroughMark(editor)}
|
||||
>
|
||||
Strikethrough
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isBlockquoteActive(editor)}
|
||||
onClick={() => editorCommands.toggleBlockquoteBlock(editor)}
|
||||
>
|
||||
Blockquote
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isCodeActive(editor)}
|
||||
onClick={() => editorCommands.toggleCodeMark(editor)}
|
||||
>
|
||||
Code
|
||||
</RichTextButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandsToolbar;
|
||||
@@ -1,7 +0,0 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.commands-toolbar {
|
||||
border-bottom: 1px solid $color-light-gray;
|
||||
padding: base(.5);
|
||||
background: $color-background-gray;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Blockquote = (props) => {
|
||||
const { attributes, children } = props;
|
||||
|
||||
return (
|
||||
<blockquote {...attributes}>{children}</blockquote>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blockquote;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H1 = ({ attributes, children }) => <h1 {...attributes}>{children}</h1>;
|
||||
|
||||
export default H1;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H2 = ({ attributes, children }) => <h2 {...attributes}>{children}</h2>;
|
||||
|
||||
export default H2;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H3 = ({ attributes, children }) => <h3 {...attributes}>{children}</h3>;
|
||||
|
||||
export default H3;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H4 = ({ attributes, children }) => <h4 {...attributes}>{children}</h4>;
|
||||
|
||||
export default H4;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H5 = ({ attributes, children }) => <h5 {...attributes}>{children}</h5>;
|
||||
|
||||
export default H5;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H6 = ({ attributes, children }) => <h6 {...attributes}>{children}</h6>;
|
||||
|
||||
export default H6;
|
||||
@@ -1,6 +0,0 @@
|
||||
export { default as H1 } from './H1';
|
||||
export { default as H2 } from './H2';
|
||||
export { default as H3 } from './H3';
|
||||
export { default as H4 } from './H4';
|
||||
export { default as H5 } from './H5';
|
||||
export { default as H6 } from './H6';
|
||||
@@ -1,14 +0,0 @@
|
||||
import {
|
||||
H1, H2, H3, H4, H5, H6,
|
||||
} from './headings';
|
||||
import Blockquote from './Blockquote';
|
||||
|
||||
export default {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
h5: H5,
|
||||
h6: H6,
|
||||
blockquote: Blockquote,
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Leaf = (props) => {
|
||||
const { children, attributes, leaf } = props;
|
||||
|
||||
const styles = {
|
||||
fontWeight: leaf.bold ? 'bold' : 'normal',
|
||||
fontStyle: leaf.italic ? 'italic' : 'normal',
|
||||
textDecoration: leaf.underline ? 'underline' : 'none',
|
||||
};
|
||||
|
||||
if (leaf.code) {
|
||||
return (
|
||||
<code {...attributes}>
|
||||
<span style={styles}>
|
||||
{ children }
|
||||
</span>
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
{...attributes}
|
||||
style={styles}
|
||||
>
|
||||
{ children }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default Leaf;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
const BlockquoteIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic blockquote"
|
||||
width="30"
|
||||
height="20"
|
||||
viewBox="0 0 30 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M0.411247 1H29.0087"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M12.8369 7.00189H29.0087"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M12.8369 12.9981H29.0087"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M0.411247 19H29.0087"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M3.45999 5.88251L9.30999 10.0394L3.45999 14.2019"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockquoteIcon;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
const BoldIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic bold"
|
||||
width="16"
|
||||
height="20"
|
||||
viewBox="0 0 16 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 0H7.59712C8.47962 0 9.3669 0.0612045 10.259 0.183616C11.1511 0.306027 11.952 0.550845 12.6619 0.918079C13.3717 1.28531 13.9472 1.79849 14.3885 2.45763C14.8297 3.11676 15.0504 3.99246 15.0504 5.08475C15.0504 6.21469 14.729 7.1516 14.0863 7.89548C13.4436 8.63936 12.5947 9.17137 11.5396 9.49153C12.211 9.64219 12.8201 9.8258 13.3669 10.0989C13.9137 10.3719 14.3837 10.7203 14.777 11.1441C15.1703 11.5678 15.4724 12.0621 15.6835 12.6271C15.8945 13.1921 16 13.7947 16 14.435C16 15.4896 15.7698 16.3701 15.3094 17.0763C14.8489 17.7825 14.2542 18.3522 13.5252 18.7853C12.7962 19.2185 11.976 19.5292 11.0647 19.7175C10.1535 19.9058 9.24701 20 8.34532 20H0V0ZM4.48921 7.99435H7.74101C8.08633 7.99435 8.42686 7.95669 8.76259 7.88136C9.09832 7.80603 9.40048 7.68362 9.66907 7.51412C9.93765 7.34463 10.1535 7.11865 10.3165 6.83616C10.4796 6.55367 10.5612 6.21469 10.5612 5.81921C10.5612 5.40489 10.47 5.06121 10.2878 4.78814C10.1055 4.51506 9.8705 4.3032 9.58273 4.15254C9.29496 4.00188 8.96883 3.8936 8.60432 3.82768C8.23981 3.76177 7.88489 3.72881 7.53957 3.72881H4.48921V7.99435ZM4.48921 16.2712H8.51799C8.86331 16.2712 9.21343 16.2335 9.56835 16.1582C9.92326 16.0829 10.2446 15.951 10.5324 15.7627C10.8201 15.5744 11.0552 15.3296 11.2374 15.0282C11.4197 14.7269 11.5108 14.3597 11.5108 13.9266C11.5108 13.4557 11.3909 13.0744 11.1511 12.7825C10.9113 12.4906 10.6091 12.2693 10.2446 12.1186C9.88009 11.968 9.48681 11.8644 9.06475 11.8079C8.64268 11.7514 8.2494 11.7232 7.88489 11.7232H4.48921V16.2712Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoldIcon;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
const CodeIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic code"
|
||||
width="27"
|
||||
height="20"
|
||||
viewBox="0 0 27 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
d="M5.9813 3.57299C6.98156 2.57274 8.5028 4.09399 7.50255 5.09424L2.59679 9.99957L7.50255 14.9053C8.5028 15.9056 6.98156 17.4268 5.9813 16.4266L0.314919 10.7602C-0.104973 10.3403 -0.104973 9.65884 0.314919 9.23895L5.9813 3.57299ZM14.4402 0.78734C14.805 -0.577307 16.8804 -0.0227584 16.5156 1.34232L11.7277 19.2127C11.3629 20.5773 9.28752 20.0228 9.65234 18.6577L14.4402 0.78734V0.78734ZM18.6658 5.09424C17.6656 4.09399 19.1868 2.57274 20.1871 3.57299L25.8534 9.23895C26.2733 9.65884 26.2733 10.3403 25.8534 10.7602L20.1871 16.4266C19.1868 17.4268 17.6656 15.9056 18.6658 14.9053L23.5716 9.99957L18.6658 5.09424V5.09424Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeIcon;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
const ItalicIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic italic"
|
||||
width="16"
|
||||
height="20"
|
||||
viewBox="0 0 16 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.64979 16.751L11.3594 3.20312C13.75 2.96875 16 2.5 16 2.5V0H4V2.5C4 2.5 5.28926 3.03719 7.35021 3.24896L4.64062 16.7969C2.25 17.0312 0 17.5 0 17.5V20H12V17.5C12 17.5 10.7107 16.9628 8.64979 16.751Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItalicIcon;
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
const OrderedListIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic ordered-list"
|
||||
width="22"
|
||||
height="20"
|
||||
viewBox="0 0 22 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
d="M21.0944 1.8183H6.6046C6.10445 1.8183 5.69899 2.22375 5.69899 2.72391V4.53513C5.69899 5.03529 6.10445 5.44075 6.6046 5.44075H21.0944C21.5945 5.44075 22 5.03529 22 4.53513V2.72391C22 2.22375 21.5945 1.8183 21.0944 1.8183Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M21.0944 9.06317H6.6046C6.10445 9.06317 5.69899 9.46863 5.69899 9.96878V11.78C5.69899 12.2802 6.10445 12.6856 6.6046 12.6856H21.0944C21.5945 12.6856 22 12.2802 22 11.78V9.96878C22 9.46863 21.5945 9.06317 21.0944 9.06317Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M21.0944 16.3081H6.6046C6.10445 16.3081 5.69899 16.7136 5.69899 17.2137V19.0249C5.69899 19.5251 6.10445 19.9306 6.6046 19.9306H21.0944C21.5945 19.9306 22 19.5251 22 19.0249V17.2137C22 16.7136 21.5945 16.3081 21.0944 16.3081Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M1.05944 0H3.23856V5.42657H2.08356V1.07007H1.05944V0Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M2.35245 9.40277C2.47688 9.24257 2.54801 9.04741 2.55585 8.84471C2.5583 8.76163 2.5439 8.67891 2.5135 8.60154C2.48311 8.52418 2.43736 8.45377 2.37901 8.39457C2.31999 8.33441 2.24908 8.28722 2.17081 8.25601C2.09253 8.22479 2.00861 8.21023 1.92439 8.21327C1.71567 8.24193 1.51773 8.32342 1.34934 8.45002C1.18094 8.57663 1.04768 8.74414 0.962176 8.93669L0 8.36715C0.21208 8.01827 0.493864 7.71689 0.827729 7.48188C1.17147 7.2702 1.57052 7.16581 1.97389 7.18207C2.42204 7.17596 2.85711 7.33313 3.19788 7.62426C3.37231 7.77118 3.51069 7.95617 3.60238 8.16499C3.69407 8.3738 3.73662 8.60088 3.72676 8.82872C3.72624 9.10824 3.6531 9.38282 3.5145 9.62555C3.29384 9.98027 3.03136 10.3072 2.7327 10.5993L1.76341 11.6075H3.88776V12.6856H0.0919738V11.7924L1.67149 10.1757C1.91697 9.93498 2.14456 9.67664 2.35245 9.40277V9.40277Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M0.337762 15.5122V14.488H3.63307V15.3194L2.53297 16.5752C2.91009 16.6195 3.25738 16.8021 3.50764 17.0876C3.7579 17.3732 3.89334 17.7415 3.88776 18.1211C3.90037 18.3718 3.85507 18.622 3.75538 18.8524C3.6557 19.0828 3.50429 19.2871 3.31291 19.4495C2.89803 19.7813 2.37731 19.9521 1.84651 19.9305C1.1934 19.9108 0.562236 19.6903 0.0389099 19.2991L0.530621 18.3439C0.919164 18.6593 1.39522 18.8476 1.89435 18.8834C2.10714 18.8921 2.31679 18.8298 2.49042 18.7065C2.56814 18.6468 2.63017 18.569 2.67116 18.48C2.71214 18.391 2.73085 18.2933 2.72568 18.1954C2.7288 18.0911 2.70565 17.9876 2.65837 17.8946C2.61109 17.8016 2.54119 17.7219 2.45508 17.6629C2.23339 17.52 1.97199 17.4512 1.70866 17.4666C1.46412 17.4813 1.22292 17.5307 0.992302 17.6134V16.7273L2.0235 15.5122H0.337762V15.5122Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderedListIcon;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
const StrikethroughIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic strikethrough"
|
||||
width="23"
|
||||
height="20"
|
||||
viewBox="0 0 23 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
d="M23 10V11.4219H17.7429C18.3531 12.2844 18.7051 13.2634 18.7051 14.289C18.7051 15.8741 17.8837 17.3893 16.452 18.4615C15.1143 19.4639 13.3776 20 11.5235 20C9.66939 20 7.93265 19.4639 6.5949 18.4615C5.16327 17.3893 4.34184 15.8741 4.34184 14.289H7.22857C7.18163 15.8275 9.15306 17.1329 11.5 17.1329C13.8469 17.1329 15.8184 15.8275 15.8184 14.289C15.8184 12.7506 13.8469 11.4219 11.5 11.4219H0V10H6.73571C6.68878 9.95338 6.64184 9.93007 6.57143 9.88345C5.1398 8.81119 4.31837 7.29604 4.31837 5.71096C4.31837 4.12587 5.1398 2.61072 6.57143 1.53846C7.90918 0.53613 9.64592 0 11.5 0C13.3541 0 15.0908 0.53613 16.4286 1.53846C17.8602 2.61072 18.6816 4.12587 18.6816 5.71096H15.8184C15.8184 4.17249 13.8469 2.86713 11.5 2.86713C9.15306 2.86713 7.18163 4.17249 7.18163 5.71096C7.18163 7.24942 9.15306 8.57809 11.5 8.57809C13.2837 8.57809 14.95 9.09091 16.2643 10H23Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default StrikethroughIcon;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
const UnderlineIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic underline"
|
||||
width="16"
|
||||
height="21"
|
||||
viewBox="0 0 16 21"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
d="M0 0.272766V8.90228C0 13.2722 3.54049 16.8127 7.91042 16.8127C12.2803 16.8127 15.8208 13.2722 15.8208 8.90228V0.272766H14.3826V8.90228C14.3826 12.5003 11.5084 15.3745 7.91042 15.3745C4.3124 15.3745 1.43826 12.5003 1.43826 8.90228V0.272766H0ZM0 18.9701V20.4083H15.8208V18.9701H0Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnderlineIcon;
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
const UnorderedListIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic unordered-list"
|
||||
width="24"
|
||||
height="20"
|
||||
viewBox="0 0 24 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="fill"
|
||||
d="M23 0H7C6.44772 0 6 0.447715 6 1V3C6 3.55228 6.44772 4 7 4H23C23.5523 4 24 3.55228 24 3V1C24 0.447715 23.5523 0 23 0Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M23 8H7C6.44772 8 6 8.44772 6 9V11C6 11.5523 6.44772 12 7 12H23C23.5523 12 24 11.5523 24 11V9C24 8.44772 23.5523 8 23 8Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M23 16H7C6.44772 16 6 16.4477 6 17V19C6 19.5523 6.44772 20 7 20H23C23.5523 20 24 19.5523 24 19V17C24 16.4477 23.5523 16 23 16Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M2 4C3.10457 4 4 3.10457 4 2C4 0.89543 3.10457 0 2 0C0.89543 0 0 0.89543 0 2C0 3.10457 0.89543 4 2 4Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M2 12C3.10457 12 4 11.1046 4 10C4 8.89543 3.10457 8 2 8C0.89543 8 0 8.89543 0 10C0 11.1046 0.89543 12 2 12Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
className="fill"
|
||||
d="M2 20C3.10457 20 4 19.1046 4 18C4 16.8954 3.10457 16 2 16C0.89543 16 0 16.8954 0 18C0 19.1046 0.89543 20 2 20Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnorderedListIcon;
|
||||
@@ -0,0 +1,8 @@
|
||||
export { default as StrikethroughIcon } from './Strikethrough';
|
||||
export { default as BlockquoteIcon } from './Blockquote';
|
||||
export { default as OrderedListIcon } from './OrderedList';
|
||||
export { default as UnorderedListIcon } from './UnorderedList';
|
||||
export { default as CodeIcon } from './Code';
|
||||
export { default as UnderlineIcon } from './Underline';
|
||||
export { default as ItalicIcon } from './Italic';
|
||||
export { default as BoldIcon } from './Bold';
|
||||
@@ -1,63 +1,201 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { createEditor } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Slate, withReact } from 'slate-react';
|
||||
import {
|
||||
ParagraphPlugin,
|
||||
BlockquotePlugin,
|
||||
BoldPlugin,
|
||||
CodePlugin,
|
||||
EditablePlugins,
|
||||
HeadingPlugin,
|
||||
ImagePlugin,
|
||||
ItalicPlugin,
|
||||
UnderlinePlugin,
|
||||
ListPlugin,
|
||||
ParagraphPlugin,
|
||||
pipe,
|
||||
getRenderElement,
|
||||
StrikethroughPlugin,
|
||||
UnderlinePlugin,
|
||||
withList,
|
||||
withToggleType,
|
||||
} from '@udecode/slate-plugins';
|
||||
|
||||
import ImageElement from '../RichTextCustom/Elements/Blockquote';
|
||||
import { richText } from '../../../../../fields/validations';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import CommandToolbar from './CommandToolbar';
|
||||
|
||||
const initialValue = [
|
||||
{
|
||||
children: [
|
||||
{ text: 'This is editable plain text, just like a <textarea>!' },
|
||||
],
|
||||
},
|
||||
];
|
||||
import './index.scss';
|
||||
|
||||
export const renderLeafItalic = () => ({ children, leaf }) => {
|
||||
if (leaf.MARK_ITALIC) {
|
||||
return (
|
||||
<em>
|
||||
FUCK:
|
||||
{' '}
|
||||
{children}
|
||||
</em>
|
||||
);
|
||||
}
|
||||
const emptyRichTextNode = [{
|
||||
children: [{ text: '' }],
|
||||
}];
|
||||
|
||||
return children;
|
||||
const enabledPluginList = {
|
||||
blockquote: options => BlockquotePlugin(options),
|
||||
bold: options => BoldPlugin(options),
|
||||
code: options => CodePlugin(options),
|
||||
heading: options => HeadingPlugin(options),
|
||||
image: options => ImagePlugin(options),
|
||||
italic: options => ItalicPlugin(options),
|
||||
list: options => ListPlugin(options),
|
||||
paragraph: options => ParagraphPlugin(options),
|
||||
strikethrough: options => StrikethroughPlugin(options),
|
||||
underline: options => UnderlinePlugin(options),
|
||||
};
|
||||
|
||||
const plugins = [ParagraphPlugin(), BoldPlugin(), UnderlinePlugin(), renderLeafItalic()];
|
||||
|
||||
const enabledPluginFunctions = [];
|
||||
const withPlugins = [withReact, withHistory];
|
||||
|
||||
const RichText = () => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const baseClass = 'rich-text';
|
||||
|
||||
const editor = useMemo(() => pipe(createEditor(), ...withPlugins), []);
|
||||
const RichText = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
style,
|
||||
width,
|
||||
label,
|
||||
placeholder,
|
||||
readOnly,
|
||||
disabledPlugins,
|
||||
disabledMarks,
|
||||
maxHeadingLevel,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
// remove config disabled plugins
|
||||
if (disabledPlugins.length > 0) {
|
||||
disabledPlugins.forEach((pluginKey) => {
|
||||
delete enabledPluginList[pluginKey];
|
||||
});
|
||||
}
|
||||
|
||||
// push the rest to enabledPlugins
|
||||
Object.keys(enabledPluginList).forEach((plugin) => {
|
||||
const options = {};
|
||||
if (plugin === 'heading' && maxHeadingLevel < 6) {
|
||||
options.levels = maxHeadingLevel;
|
||||
}
|
||||
|
||||
enabledPluginFunctions.push(enabledPluginList[plugin](options));
|
||||
});
|
||||
}, [disabledPlugins, maxHeadingLevel]);
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const fieldType = useFieldType({
|
||||
path,
|
||||
required,
|
||||
initialData,
|
||||
defaultValue,
|
||||
validate,
|
||||
});
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = fieldType;
|
||||
|
||||
const editor = useMemo(() => pipe(createEditor(), ...withPlugins, withToggleType(), withList()), []);
|
||||
|
||||
const [internalState, setInternalState] = useState(value);
|
||||
const [valueHasLoaded, setValueHasLoaded] = useState(false);
|
||||
|
||||
useEffect(() => { setValue(internalState); }, [setValue, internalState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined && !valueHasLoaded) {
|
||||
setInternalState(value);
|
||||
setValueHasLoaded(true);
|
||||
}
|
||||
}, [value, valueHasLoaded]);
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
'field-type',
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={newValue => setValue(newValue)}
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<EditablePlugins
|
||||
plugins={plugins}
|
||||
placeholder="Enter some text..."
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
</Slate>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={internalState ?? emptyRichTextNode}
|
||||
onChange={val => setInternalState(val)}
|
||||
>
|
||||
<CommandToolbar
|
||||
enabledPluginList={enabledPluginList}
|
||||
disabledMarks={disabledMarks}
|
||||
maxHeadingLevel={maxHeadingLevel}
|
||||
/>
|
||||
|
||||
<EditablePlugins
|
||||
plugins={enabledPluginFunctions}
|
||||
placeholder={placeholder}
|
||||
className={`${baseClass}__editor`}
|
||||
/>
|
||||
</Slate>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RichText.defaultProps = {
|
||||
label: null,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
defaultValue: undefined,
|
||||
initialData: undefined,
|
||||
placeholder: undefined,
|
||||
width: undefined,
|
||||
style: {},
|
||||
validate: richText,
|
||||
path: '',
|
||||
disabledPlugins: [],
|
||||
disabledMarks: [],
|
||||
maxHeadingLevel: 6,
|
||||
};
|
||||
|
||||
RichText.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
readOnly: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
initialData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
validate: PropTypes.func,
|
||||
width: PropTypes.string,
|
||||
style: PropTypes.shape({}),
|
||||
label: PropTypes.string,
|
||||
disabledPlugins: PropTypes.arrayOf(PropTypes.string),
|
||||
disabledMarks: PropTypes.arrayOf(PropTypes.string),
|
||||
maxHeadingLevel: PropTypes.number,
|
||||
};
|
||||
|
||||
export default RichText;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.rich-text-editor {
|
||||
border: 1px solid $color-light-gray;
|
||||
min-height: 250px;
|
||||
.rich-text {
|
||||
|
||||
&__editable-content-container {
|
||||
&__wrapper {
|
||||
border: 1px solid $color-light-gray;
|
||||
}
|
||||
|
||||
&__editor {
|
||||
min-height: 200px;
|
||||
padding: base(.5);
|
||||
|
||||
p,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { renderElementHeading } from './renderElementHeading';
|
||||
|
||||
export const HeadingPlugin = (options = {}) => ({
|
||||
renderElement: renderElementHeading(options),
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { headingTypes } from './types';
|
||||
|
||||
const renderElementHeading = ({
|
||||
|
||||
}) => {
|
||||
|
||||
};
|
||||
|
||||
export default renderElementHeading;
|
||||
@@ -0,0 +1,8 @@
|
||||
export const headingTypes = {
|
||||
H1: 'h1',
|
||||
H2: 'h2',
|
||||
H3: 'h3',
|
||||
H4: 'h4',
|
||||
H5: 'h5',
|
||||
H6: 'h6',
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { onKeyDownMark } from '@udecode/slate-plugins';
|
||||
|
||||
import { renderLeafTextBackground } from './renderLeafTextBackground';
|
||||
|
||||
export const TextBackgroundPlugin = (options = {}) => ({
|
||||
renderLeaf: renderLeafTextBackground(options),
|
||||
onKeyDown: onKeyDownMark(
|
||||
options.typeBackground ?? 'text-background',
|
||||
options.hotkey,
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export * from './TextBackgroundPlugin';
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const HighlightText = styled.mark`
|
||||
background-color: ${props => props.bg};
|
||||
`;
|
||||
|
||||
export const renderLeafTextBackground = ({ typeBackground = 'text-background', bg = 'red' } = {}) => ({ children, leaf }) => {
|
||||
if (leaf[typeBackground]) {
|
||||
return (
|
||||
<HighlightText
|
||||
data-slate-type={typeBackground}
|
||||
bg={bg}
|
||||
>
|
||||
{children}
|
||||
</HighlightText>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
ACTION_ITEM,
|
||||
BLOCKQUOTE,
|
||||
CODE_BLOCK,
|
||||
HeadingType,
|
||||
IMAGE,
|
||||
LINK,
|
||||
ListType,
|
||||
MARK_BOLD,
|
||||
MARK_CODE,
|
||||
MARK_HIGHLIGHT,
|
||||
MARK_ITALIC,
|
||||
MARK_SEARCH_HIGHLIGHT,
|
||||
MARK_STRIKETHROUGH,
|
||||
MARK_SUBSCRIPT,
|
||||
MARK_SUPERSCRIPT,
|
||||
MARK_UNDERLINE,
|
||||
MEDIA_EMBED,
|
||||
MENTION,
|
||||
PARAGRAPH,
|
||||
TableType,
|
||||
} from '@udecode/slate-plugins';
|
||||
|
||||
export const headingTypes = [
|
||||
HeadingType.H1,
|
||||
HeadingType.H2,
|
||||
HeadingType.H3,
|
||||
HeadingType.H4,
|
||||
HeadingType.H5,
|
||||
HeadingType.H6,
|
||||
];
|
||||
|
||||
export const nodeTypes = {
|
||||
// elements
|
||||
typeP: PARAGRAPH,
|
||||
typeMention: MENTION,
|
||||
typeBlockquote: BLOCKQUOTE,
|
||||
typeCodeBlock: CODE_BLOCK,
|
||||
typeLink: LINK,
|
||||
typeImg: IMAGE,
|
||||
typeMediaEmbed: MEDIA_EMBED,
|
||||
typeActionItem: ACTION_ITEM,
|
||||
typeTable: TableType.TABLE,
|
||||
typeTr: TableType.ROW,
|
||||
typeTd: TableType.CELL,
|
||||
typeUl: ListType.UL,
|
||||
typeOl: ListType.OL,
|
||||
typeLi: ListType.LI,
|
||||
typeH1: HeadingType.H1,
|
||||
typeH2: HeadingType.H2,
|
||||
typeH3: HeadingType.H3,
|
||||
typeH4: HeadingType.H4,
|
||||
typeH5: HeadingType.H5,
|
||||
typeH6: HeadingType.H6,
|
||||
// marks
|
||||
typeBold: MARK_BOLD,
|
||||
typeItalic: MARK_ITALIC,
|
||||
typeUnderline: MARK_UNDERLINE,
|
||||
typeStrikethrough: MARK_STRIKETHROUGH,
|
||||
typeCode: MARK_CODE,
|
||||
typeSubscript: MARK_SUBSCRIPT,
|
||||
typeSuperscript: MARK_SUPERSCRIPT,
|
||||
typeHighlight: MARK_HIGHLIGHT,
|
||||
typeSearchHighlight: MARK_SEARCH_HIGHLIGHT,
|
||||
};
|
||||
@@ -1,277 +0,0 @@
|
||||
import { Transforms, Editor, Text } from 'slate';
|
||||
import isHotKey from 'is-hotkey';
|
||||
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Keypress Commands
|
||||
--_--_--_--_--_--_-- */
|
||||
export const KEYPRESS_COMMANDS = {
|
||||
mod: isHotKey('mod'),
|
||||
bold: isHotKey('mod+b'),
|
||||
code: isHotKey('mod+shift+c'),
|
||||
italic: isHotKey('mod+i'),
|
||||
underline: isHotKey('mod+u'),
|
||||
strikethrough: isHotKey('mod+s'),
|
||||
blockquote: isHotKey('mod+shift+9'),
|
||||
};
|
||||
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Editor Checks
|
||||
--_--_--_--_--_--_-- */
|
||||
export const editorCheck = {
|
||||
|
||||
// Inline Checks
|
||||
isBoldActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.bold === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isItalicActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.italic === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isUnderlineActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.underline === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isStrikethroughActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.strikethrough === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isCodeActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.code === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
|
||||
// Block Checks
|
||||
isBlockquoteActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'blockquote',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH1Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h1',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH2Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h2',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH3Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h3',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH4Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h4',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH5Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h5',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH6Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h6',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
//! only apply styles to cursor or selection
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Editor Commands
|
||||
--_--_--_--_--_--_-- */
|
||||
export const editorCommands = {
|
||||
|
||||
// Inline Marks/Leafs
|
||||
toggleBoldMark(editor) {
|
||||
const boldIsActive = !!editorCheck.isBoldActive(editor);
|
||||
console.log('boldIsActive', boldIsActive);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: boldIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleItalicMark(editor) {
|
||||
const italicIsActive = editorCheck.isItalicActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ italic: italicIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleUnderlineMark(editor) {
|
||||
const underlineIsActive = editorCheck.isUnderlineActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{
|
||||
underline: underlineIsActive ? null : true,
|
||||
strikethrough: false,
|
||||
},
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleStrikethroughMark(editor) {
|
||||
const strikethroughIsActive = editorCheck.isStrikethroughActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{
|
||||
strikethrough: strikethroughIsActive ? null : true,
|
||||
underline: false,
|
||||
},
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleCodeMark(editor) {
|
||||
const codeMarkIsActive = editorCheck.isCodeActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ code: codeMarkIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
// Toggle Blocks
|
||||
toggleBlockquoteBlock(editor) {
|
||||
const blockQuoteActive = editorCheck.isBlockquoteActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: blockQuoteActive ? null : 'blockquote' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH1Block(editor) {
|
||||
const h1IsActive = editorCheck.isH1Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h1IsActive ? null : 'h1' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH2Block(editor) {
|
||||
const isActive = editorCheck.isH2Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: isActive ? null : 'h2' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH3Block(editor) {
|
||||
const h3IsActive = editorCheck.isH3Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h3IsActive ? null : 'h3' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH4Block(editor) {
|
||||
const h4IsActive = editorCheck.isH4Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h4IsActive ? null : 'h4' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH5Block(editor) {
|
||||
const h5IsActive = editorCheck.isH5Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h5IsActive ? null : 'h5' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH6Block(editor) {
|
||||
const h6IsActive = editorCheck.isH6Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h6IsActive ? null : 'h6' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Button from '../../../../../elements/Button';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'command-button';
|
||||
|
||||
const CommandButton = (props) => {
|
||||
const { isActive, children } = props;
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
isActive && `${baseClass}--is-active`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={classes}
|
||||
size="small"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
CommandButton.defaultProps = {
|
||||
isActive: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
CommandButton.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default CommandButton;
|
||||
@@ -1,99 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useSlate } from 'slate-react';
|
||||
|
||||
import RichTextButton from './CommandButton';
|
||||
import { editorCheck, editorCommands } from '../utils';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'commands-toolbar';
|
||||
|
||||
const CommandsToolbar = (props) => {
|
||||
const { className } = props;
|
||||
const editor = useSlate();
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
className && className,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH1Active(editor)}
|
||||
onClick={() => editorCommands.toggleH1Block(editor)}
|
||||
>
|
||||
H1
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH2Active(editor)}
|
||||
onClick={() => editorCommands.toggleH2Block(editor)}
|
||||
>
|
||||
H2
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH3Active(editor)}
|
||||
onClick={() => editorCommands.toggleH3Block(editor)}
|
||||
>
|
||||
H3
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH4Active(editor)}
|
||||
onClick={() => editorCommands.toggleH4Block(editor)}
|
||||
>
|
||||
H4
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH5Active(editor)}
|
||||
onClick={() => editorCommands.toggleH5Block(editor)}
|
||||
>
|
||||
H5
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isH6Active(editor)}
|
||||
onClick={() => editorCommands.toggleH6Block(editor)}
|
||||
>
|
||||
H6
|
||||
</RichTextButton>
|
||||
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isBoldActive(editor)}
|
||||
onClick={() => editorCommands.toggleBoldMark(editor)}
|
||||
>
|
||||
Bold
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isItalicActive(editor)}
|
||||
onClick={() => editorCommands.toggleItalicMark(editor)}
|
||||
>
|
||||
Italic
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isUnderlineActive(editor)}
|
||||
onClick={() => editorCommands.toggleUnderlineMark(editor)}
|
||||
>
|
||||
Underline
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isStrikethroughActive(editor)}
|
||||
onClick={() => editorCommands.toggleStrikethroughMark(editor)}
|
||||
>
|
||||
Strikethrough
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isBlockquoteActive(editor)}
|
||||
onClick={() => editorCommands.toggleBlockquoteBlock(editor)}
|
||||
>
|
||||
Blockquote
|
||||
</RichTextButton>
|
||||
<RichTextButton
|
||||
isActive={editorCheck.isCodeActive(editor)}
|
||||
onClick={() => editorCommands.toggleCodeMark(editor)}
|
||||
>
|
||||
Code
|
||||
</RichTextButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandsToolbar;
|
||||
@@ -1,7 +0,0 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.commands-toolbar {
|
||||
border-bottom: 1px solid $color-light-gray;
|
||||
padding: base(.5);
|
||||
background: $color-background-gray;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Blockquote = (props) => {
|
||||
const { attributes, children } = props;
|
||||
|
||||
return (
|
||||
<blockquote {...attributes}>{children}</blockquote>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blockquote;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H1 = ({ attributes, children }) => <h1 {...attributes}>{children}</h1>;
|
||||
|
||||
export default H1;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H2 = ({ attributes, children }) => <h2 {...attributes}>{children}</h2>;
|
||||
|
||||
export default H2;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H3 = ({ attributes, children }) => <h3 {...attributes}>{children}</h3>;
|
||||
|
||||
export default H3;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H4 = ({ attributes, children }) => <h4 {...attributes}>{children}</h4>;
|
||||
|
||||
export default H4;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H5 = ({ attributes, children }) => <h5 {...attributes}>{children}</h5>;
|
||||
|
||||
export default H5;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const H6 = ({ attributes, children }) => <h6 {...attributes}>{children}</h6>;
|
||||
|
||||
export default H6;
|
||||
@@ -1,6 +0,0 @@
|
||||
export { default as H1 } from './H1';
|
||||
export { default as H2 } from './H2';
|
||||
export { default as H3 } from './H3';
|
||||
export { default as H4 } from './H4';
|
||||
export { default as H5 } from './H5';
|
||||
export { default as H6 } from './H6';
|
||||
@@ -1,14 +0,0 @@
|
||||
import {
|
||||
H1, H2, H3, H4, H5, H6,
|
||||
} from './headings';
|
||||
import Blockquote from './Blockquote';
|
||||
|
||||
export default {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
h5: H5,
|
||||
h6: H6,
|
||||
blockquote: Blockquote,
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Leaf = (props) => {
|
||||
const { children, attributes, leaf } = props;
|
||||
|
||||
const styles = {
|
||||
fontWeight: leaf.bold ? 'bold' : 'normal',
|
||||
fontStyle: leaf.italic ? 'italic' : 'normal',
|
||||
textDecoration: leaf.underline ? 'underline' : 'none',
|
||||
};
|
||||
|
||||
if (leaf.code) {
|
||||
return (
|
||||
<code {...attributes}>
|
||||
<span style={styles}>
|
||||
{ children }
|
||||
</span>
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
{...attributes}
|
||||
style={styles}
|
||||
>
|
||||
{ children }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default Leaf;
|
||||
@@ -1,11 +0,0 @@
|
||||
1. Paste html
|
||||
- need to create a serializer
|
||||
1. Copy content -> what happens? Is it HTML? Can you paste it elsewhere?
|
||||
- need to create a deserializer (I think)
|
||||
1. Ability to customize inline references to user-defined schemas like Button, etc. This should be extendable via config
|
||||
1. We should be able to embed complex schemas like tables or referenced documents
|
||||
1. Provision for a way to "assemble" HTML from RichText and export it from Payload
|
||||
|
||||
|
||||
Typing in italic
|
||||
-> toggling it off, toggles previous string, not just the cursor.
|
||||
@@ -1,130 +0,0 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { createEditor } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Slate, Editable, withReact } from 'slate-react';
|
||||
|
||||
import CommandsToolbar from './CommandsToolbar';
|
||||
import { KEYPRESS_COMMANDS, editorCommands } from './utils';
|
||||
import elements from './Elements';
|
||||
import Leaf from './Leaf';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'rich-text-editor';
|
||||
|
||||
const emptyNode = [{
|
||||
type: 'paragraph',
|
||||
children: [{ text: '' }],
|
||||
}];
|
||||
|
||||
const RichText = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
defaultValue,
|
||||
initialData,
|
||||
validate,
|
||||
style,
|
||||
width,
|
||||
label,
|
||||
placeholder,
|
||||
readOnly,
|
||||
} = props;
|
||||
|
||||
const editor = useMemo(() => withReact(withHistory(createEditor())), []);
|
||||
|
||||
//! only for ease of testing
|
||||
const storedData = JSON.parse(localStorage.getItem('rich-text-content'));
|
||||
const intialEditorContent = storedData || emptyNode;
|
||||
|
||||
const [value, setValue] = useState(intialEditorContent);
|
||||
|
||||
// const fieldType = useFieldType({
|
||||
// path,
|
||||
// required,
|
||||
// initialData,
|
||||
// defaultValue,
|
||||
// validate,
|
||||
// });
|
||||
|
||||
const renderBlockElement = useCallback((elementProps) => {
|
||||
const {
|
||||
element: {
|
||||
type,
|
||||
},
|
||||
attributes,
|
||||
children,
|
||||
} = elementProps;
|
||||
|
||||
const ElementToRender = elements[type];
|
||||
|
||||
if (ElementToRender) return <ElementToRender attributes={attributes}>{children}</ElementToRender>;
|
||||
|
||||
return <p {...attributes}>{children}</p>;
|
||||
}, []);
|
||||
|
||||
const renderLeaf = useCallback((elementProps) => {
|
||||
return <Leaf {...elementProps} />;
|
||||
}, []);
|
||||
|
||||
const classes = [baseClass].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
setValue(newValue);
|
||||
|
||||
//! only for ease of testing
|
||||
localStorage.setItem('rich-text-content', JSON.stringify(newValue));
|
||||
}}
|
||||
>
|
||||
<div className={classes}>
|
||||
<CommandsToolbar />
|
||||
|
||||
<Editable
|
||||
className={`${baseClass}__editable-content-container`}
|
||||
renderElement={renderBlockElement}
|
||||
renderLeaf={renderLeaf}
|
||||
onKeyDown={(event) => {
|
||||
if (!event.metaKey) return;
|
||||
|
||||
if (KEYPRESS_COMMANDS.code(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleCodeMark(editor);
|
||||
}
|
||||
|
||||
if (KEYPRESS_COMMANDS.bold(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleBoldMark(editor);
|
||||
}
|
||||
|
||||
if (KEYPRESS_COMMANDS.italic(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleItalicMark(editor);
|
||||
}
|
||||
|
||||
if (KEYPRESS_COMMANDS.underline(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleUnderlineMark(editor);
|
||||
}
|
||||
|
||||
if (KEYPRESS_COMMANDS.strikethrough(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleStrikethroughMark(editor);
|
||||
}
|
||||
|
||||
if (KEYPRESS_COMMANDS.blockquote(event)) {
|
||||
event.preventDefault();
|
||||
editorCommands.toggleBlockquoteBlock(editor);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Slate>
|
||||
);
|
||||
};
|
||||
|
||||
export default RichText;
|
||||
@@ -1,17 +0,0 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.rich-text-editor {
|
||||
border: 1px solid $color-light-gray;
|
||||
min-height: 250px;
|
||||
|
||||
&__editable-content-container {
|
||||
padding: base(.5);
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin-bottom: base(.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
import { Transforms, Editor, Text } from 'slate';
|
||||
import isHotKey from 'is-hotkey';
|
||||
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Keypress Commands
|
||||
--_--_--_--_--_--_-- */
|
||||
export const KEYPRESS_COMMANDS = {
|
||||
mod: isHotKey('mod'),
|
||||
bold: isHotKey('mod+b'),
|
||||
code: isHotKey('mod+shift+c'),
|
||||
italic: isHotKey('mod+i'),
|
||||
underline: isHotKey('mod+u'),
|
||||
strikethrough: isHotKey('mod+s'),
|
||||
blockquote: isHotKey('mod+shift+9'),
|
||||
};
|
||||
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Editor Checks
|
||||
--_--_--_--_--_--_-- */
|
||||
export const editorCheck = {
|
||||
|
||||
// Inline Checks
|
||||
isBoldActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.bold === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isItalicActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.italic === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isUnderlineActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.underline === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isStrikethroughActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.strikethrough === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isCodeActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.code === true,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
|
||||
// Block Checks
|
||||
isBlockquoteActive(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'blockquote',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH1Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h1',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH2Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h2',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH3Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h3',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH4Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h4',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH5Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h5',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
|
||||
isH6Active(editor) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: node => node.type === 'h6',
|
||||
});
|
||||
|
||||
return !!match;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
//! only apply styles to cursor or selection
|
||||
|
||||
/* --_--_--_--_--_--_--
|
||||
Editor Commands
|
||||
--_--_--_--_--_--_-- */
|
||||
export const editorCommands = {
|
||||
|
||||
// Inline Marks/Leafs
|
||||
toggleBoldMark(editor) {
|
||||
const boldIsActive = !!editorCheck.isBoldActive(editor);
|
||||
console.log('boldIsActive', boldIsActive);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: boldIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleItalicMark(editor) {
|
||||
const italicIsActive = editorCheck.isItalicActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ italic: italicIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleUnderlineMark(editor) {
|
||||
const underlineIsActive = editorCheck.isUnderlineActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{
|
||||
underline: underlineIsActive ? null : true,
|
||||
strikethrough: false,
|
||||
},
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleStrikethroughMark(editor) {
|
||||
const strikethroughIsActive = editorCheck.isStrikethroughActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{
|
||||
strikethrough: strikethroughIsActive ? null : true,
|
||||
underline: false,
|
||||
},
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
toggleCodeMark(editor) {
|
||||
const codeMarkIsActive = editorCheck.isCodeActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ code: codeMarkIsActive ? null : true },
|
||||
{
|
||||
match: node => Text.isText(node),
|
||||
split: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
// Toggle Blocks
|
||||
toggleBlockquoteBlock(editor) {
|
||||
const blockQuoteActive = editorCheck.isBlockquoteActive(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: blockQuoteActive ? null : 'blockquote' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH1Block(editor) {
|
||||
const h1IsActive = editorCheck.isH1Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h1IsActive ? null : 'h1' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH2Block(editor) {
|
||||
const isActive = editorCheck.isH2Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: isActive ? null : 'h2' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH3Block(editor) {
|
||||
const h3IsActive = editorCheck.isH3Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h3IsActive ? null : 'h3' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH4Block(editor) {
|
||||
const h4IsActive = editorCheck.isH4Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h4IsActive ? null : 'h4' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH5Block(editor) {
|
||||
const h5IsActive = editorCheck.isH5Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h5IsActive ? null : 'h5' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
toggleH6Block(editor) {
|
||||
const h6IsActive = editorCheck.isH6Active(editor);
|
||||
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: h6IsActive ? null : 'h6' },
|
||||
{ match: node => Editor.isBlock(editor, node) },
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
@@ -62,7 +62,13 @@ const optionsToValidatorMap = {
|
||||
return Boolean(value);
|
||||
},
|
||||
richText: (value) => {
|
||||
if (value) return true;
|
||||
//! Need better way to share an empty text node
|
||||
//! it is used here and in field-types/RichText
|
||||
const emptyRichTextNode = [{
|
||||
children: [{ text: '' }],
|
||||
}];
|
||||
const blankSlateJSNode = JSON.stringify(emptyRichTextNode);
|
||||
if (value && JSON.stringify(value) !== blankSlateJSNode) return true;
|
||||
return 'This field is required.';
|
||||
},
|
||||
code: (value) => {
|
||||
|
||||
Reference in New Issue
Block a user