@plone/volto-slate 18.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +6 -0
- package/.release-it.json +25 -0
- package/CHANGELOG.md +19 -0
- package/LICENSE.md +21 -0
- package/README.md +10 -0
- package/build/messages/src/blocks/Table/TableBlockEdit.json +90 -0
- package/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +6 -0
- package/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +6 -0
- package/build/messages/src/blocks/Text/SlashMenu.json +6 -0
- package/build/messages/src/editor/plugins/AdvancedLink/index.json +10 -0
- package/build/messages/src/editor/plugins/Link/index.json +10 -0
- package/build/messages/src/editor/plugins/Table/index.json +30 -0
- package/build/messages/src/elementEditor/messages.json +10 -0
- package/build/messages/src/widgets/HtmlSlateWidget.json +6 -0
- package/build/messages/src/widgets/RichTextWidgetView.json +6 -0
- package/locales/de/LC_MESSAGES/volto.po +148 -0
- package/locales/en/LC_MESSAGES/volto.po +148 -0
- package/locales/volto.pot +182 -0
- package/package.json +42 -0
- package/src/actions/content.js +30 -0
- package/src/actions/index.js +3 -0
- package/src/actions/plugins.js +9 -0
- package/src/actions/selection.js +22 -0
- package/src/blocks/Table/Cell.jsx +87 -0
- package/src/blocks/Table/Cell.test.js +54 -0
- package/src/blocks/Table/TableBlockEdit.jsx +694 -0
- package/src/blocks/Table/TableBlockEdit.test.js +40 -0
- package/src/blocks/Table/TableBlockView.jsx +150 -0
- package/src/blocks/Table/TableBlockView.test.js +49 -0
- package/src/blocks/Table/__snapshots__/Cell.test.js.snap +3 -0
- package/src/blocks/Table/__snapshots__/TableBlockEdit.test.js.snap +22 -0
- package/src/blocks/Table/__snapshots__/TableBlockView.test.js.snap +27 -0
- package/src/blocks/Table/deconstruct.js +113 -0
- package/src/blocks/Table/extensions/normalizeTable.js +5 -0
- package/src/blocks/Table/index.js +60 -0
- package/src/blocks/Table/schema.js +122 -0
- package/src/blocks/Text/DefaultTextBlockEditor.jsx +304 -0
- package/src/blocks/Text/DetachedTextBlockEditor.jsx +77 -0
- package/src/blocks/Text/MarkdownIntroduction.jsx +59 -0
- package/src/blocks/Text/PluginSidebar.jsx +18 -0
- package/src/blocks/Text/ShortcutListing.jsx +28 -0
- package/src/blocks/Text/SlashMenu.jsx +203 -0
- package/src/blocks/Text/TextBlockEdit.jsx +38 -0
- package/src/blocks/Text/TextBlockEdit.test.js +107 -0
- package/src/blocks/Text/TextBlockSchema.js +54 -0
- package/src/blocks/Text/TextBlockView.jsx +31 -0
- package/src/blocks/Text/__snapshots__/TextBlockEdit.test.js.snap +62 -0
- package/src/blocks/Text/css/editor.css +18 -0
- package/src/blocks/Text/extensions/Readme.md +49 -0
- package/src/blocks/Text/extensions/breakList.js +100 -0
- package/src/blocks/Text/extensions/index.js +6 -0
- package/src/blocks/Text/extensions/insertBreak.js +57 -0
- package/src/blocks/Text/extensions/isSelected.js +7 -0
- package/src/blocks/Text/extensions/normalizeExternalData.js +7 -0
- package/src/blocks/Text/extensions/withDeserializers.js +87 -0
- package/src/blocks/Text/extensions/withLists.js +5 -0
- package/src/blocks/Text/index.js +171 -0
- package/src/blocks/Text/keyboard/backspaceInList.js +58 -0
- package/src/blocks/Text/keyboard/breakBlocks.js +3 -0
- package/src/blocks/Text/keyboard/cancelEsc.js +7 -0
- package/src/blocks/Text/keyboard/indentListItems.js +240 -0
- package/src/blocks/Text/keyboard/index.js +52 -0
- package/src/blocks/Text/keyboard/joinBlocks.js +180 -0
- package/src/blocks/Text/keyboard/moveListItems.js +124 -0
- package/src/blocks/Text/keyboard/slashMenu.js +19 -0
- package/src/blocks/Text/keyboard/softBreak.js +7 -0
- package/src/blocks/Text/keyboard/traverseBlocks.js +81 -0
- package/src/blocks/Text/keyboard/unwrapEmptyString.js +26 -0
- package/src/blocks/Text/schema.js +39 -0
- package/src/constants.js +123 -0
- package/src/editor/EditorContext.jsx +5 -0
- package/src/editor/EditorReference.jsx +22 -0
- package/src/editor/SlateEditor.jsx +375 -0
- package/src/editor/config.jsx +344 -0
- package/src/editor/decorate.js +68 -0
- package/src/editor/deserialize.js +185 -0
- package/src/editor/extensions/index.js +6 -0
- package/src/editor/extensions/insertBreak.js +15 -0
- package/src/editor/extensions/insertData.js +161 -0
- package/src/editor/extensions/isInline.js +14 -0
- package/src/editor/extensions/normalizeExternalData.js +8 -0
- package/src/editor/extensions/normalizeNode.js +48 -0
- package/src/editor/extensions/withDeserializers.js +15 -0
- package/src/editor/extensions/withTestingFeatures.jsx +84 -0
- package/src/editor/index.js +14 -0
- package/src/editor/less/editor.less +173 -0
- package/src/editor/less/globals.less +18 -0
- package/src/editor/less/slate.less +28 -0
- package/src/editor/plugins/AdvancedLink/deserialize.js +90 -0
- package/src/editor/plugins/AdvancedLink/extensions.js +32 -0
- package/src/editor/plugins/AdvancedLink/index.js +50 -0
- package/src/editor/plugins/AdvancedLink/render.jsx +37 -0
- package/src/editor/plugins/AdvancedLink/schema.js +114 -0
- package/src/editor/plugins/AdvancedLink/styles.less +8 -0
- package/src/editor/plugins/Blockquote/index.js +30 -0
- package/src/editor/plugins/Callout/index.js +34 -0
- package/src/editor/plugins/Image/deconstruct.js +30 -0
- package/src/editor/plugins/Image/extensions.js +51 -0
- package/src/editor/plugins/Image/index.js +11 -0
- package/src/editor/plugins/Image/render.jsx +22 -0
- package/src/editor/plugins/Link/extensions.js +58 -0
- package/src/editor/plugins/Link/index.js +159 -0
- package/src/editor/plugins/Link/render.jsx +54 -0
- package/src/editor/plugins/Markdown/constants.js +81 -0
- package/src/editor/plugins/Markdown/extensions.js +336 -0
- package/src/editor/plugins/Markdown/index.js +28 -0
- package/src/editor/plugins/Markdown/utils.js +198 -0
- package/src/editor/plugins/StyleMenu/StyleMenu.jsx +153 -0
- package/src/editor/plugins/StyleMenu/index.js +19 -0
- package/src/editor/plugins/StyleMenu/style.less +29 -0
- package/src/editor/plugins/StyleMenu/utils.js +168 -0
- package/src/editor/plugins/Table/TableButton.jsx +142 -0
- package/src/editor/plugins/Table/TableCell.jsx +44 -0
- package/src/editor/plugins/Table/TableContainer.jsx +37 -0
- package/src/editor/plugins/Table/TableSizePicker.jsx +83 -0
- package/src/editor/plugins/Table/extensions.js +87 -0
- package/src/editor/plugins/Table/index.js +390 -0
- package/src/editor/plugins/Table/less/public.less +29 -0
- package/src/editor/plugins/Table/less/table.less +28 -0
- package/src/editor/plugins/Table/render.jsx +30 -0
- package/src/editor/plugins/index.js +19 -0
- package/src/editor/render.jsx +224 -0
- package/src/editor/ui/BasicToolbar.jsx +11 -0
- package/src/editor/ui/BlockButton.jsx +31 -0
- package/src/editor/ui/ClearFormattingButton.jsx +21 -0
- package/src/editor/ui/ExpandedToolbar.jsx +18 -0
- package/src/editor/ui/Expando.jsx +5 -0
- package/src/editor/ui/InlineToolbar.jsx +69 -0
- package/src/editor/ui/MarkButton.jsx +23 -0
- package/src/editor/ui/MarkElementButton.jsx +30 -0
- package/src/editor/ui/Menu.jsx +13 -0
- package/src/editor/ui/PositionedToolbar.jsx +32 -0
- package/src/editor/ui/Separator.jsx +7 -0
- package/src/editor/ui/SlateContextToolbar.jsx +13 -0
- package/src/editor/ui/SlateToolbar.jsx +96 -0
- package/src/editor/ui/Toolbar.jsx +103 -0
- package/src/editor/ui/ToolbarButton.jsx +33 -0
- package/src/editor/ui/ToolbarButton.test.js +25 -0
- package/src/editor/ui/__snapshots__/ToolbarButton.test.js.snap +16 -0
- package/src/editor/ui/index.js +15 -0
- package/src/editor/utils.js +248 -0
- package/src/elementEditor/ContextButtons.jsx +57 -0
- package/src/elementEditor/PluginEditor.jsx +124 -0
- package/src/elementEditor/Readme.md +6 -0
- package/src/elementEditor/SchemaProvider.jsx +4 -0
- package/src/elementEditor/SidebarEditor.jsx +46 -0
- package/src/elementEditor/ToolbarButton.jsx +44 -0
- package/src/elementEditor/index.js +5 -0
- package/src/elementEditor/makeInlineElementPlugin.js +100 -0
- package/src/elementEditor/messages.js +14 -0
- package/src/elementEditor/utils.js +227 -0
- package/src/hooks/index.js +3 -0
- package/src/hooks/useEditorContext.js +6 -0
- package/src/hooks/useIsomorphicLayoutEffect.js +7 -0
- package/src/hooks/useSelectionPosition.js +25 -0
- package/src/i18n.js +180 -0
- package/src/icons/hashlink.svg +57 -0
- package/src/index.js +61 -0
- package/src/reducers/content.js +74 -0
- package/src/reducers/index.js +3 -0
- package/src/reducers/plugins.js +17 -0
- package/src/reducers/selection.js +16 -0
- package/src/utils/blocks.js +379 -0
- package/src/utils/blocks.test.js +138 -0
- package/src/utils/editor.js +31 -0
- package/src/utils/image.js +25 -0
- package/src/utils/index.js +11 -0
- package/src/utils/internals.js +46 -0
- package/src/utils/lists.js +92 -0
- package/src/utils/marks.js +104 -0
- package/src/utils/mime-types.js +24 -0
- package/src/utils/nodes.js +4 -0
- package/src/utils/ops.js +20 -0
- package/src/utils/random.js +17 -0
- package/src/utils/selection.js +236 -0
- package/src/utils/slate-string-utils.js +409 -0
- package/src/utils/volto-blocks.js +314 -0
- package/src/widgets/ErrorBoundary.jsx +27 -0
- package/src/widgets/HtmlSlateWidget.jsx +138 -0
- package/src/widgets/ObjectByTypeWidget.jsx +49 -0
- package/src/widgets/RichTextWidget.jsx +72 -0
- package/src/widgets/RichTextWidgetView.jsx +36 -0
- package/src/widgets/style.css +21 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import { toast } from 'react-toastify';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
6
|
+
import { useSelector } from 'react-redux';
|
|
7
|
+
import { Node, Text } from 'slate';
|
|
8
|
+
import cx from 'classnames';
|
|
9
|
+
import { isEmpty, omit } from 'lodash';
|
|
10
|
+
import { UniversalLink, Toast } from '@plone/volto/components';
|
|
11
|
+
import { messages, addAppURL } from '@plone/volto/helpers';
|
|
12
|
+
import useClipboard from '@plone/volto/hooks/clipboard/useClipboard';
|
|
13
|
+
import config from '@plone/volto/registry';
|
|
14
|
+
import linkSVG from '@plone/volto/icons/link.svg';
|
|
15
|
+
|
|
16
|
+
import './less/slate.less';
|
|
17
|
+
|
|
18
|
+
const OMITTED = ['editor', 'path'];
|
|
19
|
+
|
|
20
|
+
// TODO: read, see if relevant
|
|
21
|
+
// https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method
|
|
22
|
+
export const Element = ({ element, attributes = {}, extras, ...rest }) => {
|
|
23
|
+
const { slate } = config.settings;
|
|
24
|
+
const { elements } = slate;
|
|
25
|
+
const El = elements[element.type] || elements['default'];
|
|
26
|
+
|
|
27
|
+
const attrs = Object.assign(
|
|
28
|
+
element.styleName ? { className: element.styleName } : {},
|
|
29
|
+
...Object.keys(attributes || {}).map((k) =>
|
|
30
|
+
!isEmpty(attributes[k]) ? { [k]: attributes[k] } : {},
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
attrs.ref = attributes?.ref; // never remove the ref
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<El
|
|
37
|
+
element={element}
|
|
38
|
+
{...omit(rest, OMITTED)}
|
|
39
|
+
attributes={attrs}
|
|
40
|
+
extras={extras}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Leaf = ({ children, ...rest }) => {
|
|
46
|
+
const { attributes, leaf, mode } = rest;
|
|
47
|
+
let { leafs } = config.settings.slate;
|
|
48
|
+
|
|
49
|
+
children = Object.keys(leafs).reduce((acc, name) => {
|
|
50
|
+
return Object.keys(leaf).includes(name)
|
|
51
|
+
? leafs[name]({ children: acc })
|
|
52
|
+
: acc;
|
|
53
|
+
}, children);
|
|
54
|
+
|
|
55
|
+
const classNames = {
|
|
56
|
+
[`highlight-${leaf.highlightType}`]: mode !== 'view' && leaf.highlightType,
|
|
57
|
+
'highlight-selection': mode !== 'view' && leaf.isSelection,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// stylemenu support
|
|
61
|
+
for (const prop in leaf) {
|
|
62
|
+
if (prop.startsWith('style-')) {
|
|
63
|
+
classNames[prop.substring(6)] = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const klass = cx(classNames);
|
|
68
|
+
|
|
69
|
+
return mode === 'view' ? (
|
|
70
|
+
typeof children === 'string' ? (
|
|
71
|
+
children.split('\n').map((t, i) => {
|
|
72
|
+
// Softbreak support. Should do a plugin?
|
|
73
|
+
return (
|
|
74
|
+
<React.Fragment key={`${i}`}>
|
|
75
|
+
{children.indexOf('\n') > -1 &&
|
|
76
|
+
children.split('\n').length - 1 > i ? (
|
|
77
|
+
<>
|
|
78
|
+
{klass ? <span className={klass}>{t}</span> : t}
|
|
79
|
+
<br />
|
|
80
|
+
</>
|
|
81
|
+
) : klass ? (
|
|
82
|
+
<span className={klass}>{t}</span>
|
|
83
|
+
) : (
|
|
84
|
+
t
|
|
85
|
+
)}
|
|
86
|
+
</React.Fragment>
|
|
87
|
+
);
|
|
88
|
+
})
|
|
89
|
+
) : (
|
|
90
|
+
<span className={klass}>{children}</span>
|
|
91
|
+
)
|
|
92
|
+
) : (
|
|
93
|
+
<span {...attributes} className={klass}>
|
|
94
|
+
{children}
|
|
95
|
+
</span>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const serializeData = (node) => {
|
|
100
|
+
return JSON.stringify({ type: node.type, data: node.data });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const serializeNodes = (nodes, getAttributes, extras = {}) => {
|
|
104
|
+
const editor = { children: nodes || [] };
|
|
105
|
+
|
|
106
|
+
const _serializeNodes = (nodes) => {
|
|
107
|
+
return (nodes || []).map(([node, path], i) => {
|
|
108
|
+
return Text.isText(node) ? (
|
|
109
|
+
<Leaf path={path} leaf={node} text={node} mode="view" key={path}>
|
|
110
|
+
{node.text}
|
|
111
|
+
</Leaf>
|
|
112
|
+
) : (
|
|
113
|
+
<Element
|
|
114
|
+
path={path}
|
|
115
|
+
element={node}
|
|
116
|
+
mode="view"
|
|
117
|
+
key={path}
|
|
118
|
+
data-slate-data={node.data ? serializeData(node) : null}
|
|
119
|
+
attributes={getAttributes ? getAttributes(node, path) : null}
|
|
120
|
+
extras={extras}
|
|
121
|
+
>
|
|
122
|
+
{_serializeNodes(Array.from(Node.children(editor, path)))}
|
|
123
|
+
</Element>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return _serializeNodes(Array.from(Node.children(editor, [])));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the concatenated text string of a node's content.
|
|
133
|
+
*
|
|
134
|
+
* Note that this WILL include spaces between block node leafs in contrary to
|
|
135
|
+
* the original slate method. This function joins text of nodes with
|
|
136
|
+
* separating spaces to produce a string for indexing purposes.
|
|
137
|
+
*
|
|
138
|
+
*/
|
|
139
|
+
const ConcatenatedString = (node) => {
|
|
140
|
+
if (Text.isText(node)) {
|
|
141
|
+
return node.text.trim();
|
|
142
|
+
} else {
|
|
143
|
+
return node.children.map(ConcatenatedString).join(' ');
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @function serializeNodesToText
|
|
149
|
+
*
|
|
150
|
+
* @param {Array[Node]} nodes
|
|
151
|
+
*
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
export const serializeNodesToText = (nodes) => {
|
|
155
|
+
return nodes.map(ConcatenatedString).join('\n');
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const serializeNodesToHtml = (nodes) =>
|
|
159
|
+
renderToStaticMarkup(serializeNodes(nodes));
|
|
160
|
+
|
|
161
|
+
export const renderLinkElement = (tagName) => {
|
|
162
|
+
function LinkElement({
|
|
163
|
+
attributes,
|
|
164
|
+
children,
|
|
165
|
+
mode = 'edit',
|
|
166
|
+
className = null,
|
|
167
|
+
}) {
|
|
168
|
+
const { slate = {} } = config.settings;
|
|
169
|
+
const Tag = tagName;
|
|
170
|
+
const slug = attributes.id || '';
|
|
171
|
+
const location = useLocation();
|
|
172
|
+
const token = useSelector((state) => state.userSession.token);
|
|
173
|
+
const appPathname = addAppURL(location.pathname);
|
|
174
|
+
// eslint-disable-next-line no-unused-vars
|
|
175
|
+
const [copied, copy, setCopied] = useClipboard(
|
|
176
|
+
appPathname.concat(`#${slug}`),
|
|
177
|
+
);
|
|
178
|
+
const intl = useIntl();
|
|
179
|
+
return !token || slate.useLinkedHeadings === false ? (
|
|
180
|
+
<Tag {...attributes} className={className} tabIndex={0}>
|
|
181
|
+
{children}
|
|
182
|
+
</Tag>
|
|
183
|
+
) : (
|
|
184
|
+
<Tag {...attributes} className={className} tabIndex={0}>
|
|
185
|
+
{children}
|
|
186
|
+
{mode === 'view' && slug && (
|
|
187
|
+
<UniversalLink
|
|
188
|
+
className="anchor"
|
|
189
|
+
aria-hidden="true"
|
|
190
|
+
tabIndex={-1}
|
|
191
|
+
href={`#${slug}`}
|
|
192
|
+
>
|
|
193
|
+
<style>
|
|
194
|
+
{/* Prettify the unstyled flash of the link icon on development */}
|
|
195
|
+
{`
|
|
196
|
+
a.anchor svg {
|
|
197
|
+
height: var(--anchor-svg-height, 24px);
|
|
198
|
+
}
|
|
199
|
+
`}
|
|
200
|
+
</style>
|
|
201
|
+
<svg
|
|
202
|
+
{...linkSVG.attributes}
|
|
203
|
+
dangerouslySetInnerHTML={{ __html: linkSVG.content }}
|
|
204
|
+
height={null}
|
|
205
|
+
onClick={() => {
|
|
206
|
+
copy();
|
|
207
|
+
|
|
208
|
+
toast.info(
|
|
209
|
+
<Toast
|
|
210
|
+
info
|
|
211
|
+
title={intl.formatMessage(messages.success)}
|
|
212
|
+
content={intl.formatMessage(messages.urlClipboardCopy)}
|
|
213
|
+
/>,
|
|
214
|
+
);
|
|
215
|
+
}}
|
|
216
|
+
></svg>
|
|
217
|
+
</UniversalLink>
|
|
218
|
+
)}
|
|
219
|
+
</Tag>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
LinkElement.displayName = `${tagName}LinkElement`;
|
|
223
|
+
return LinkElement;
|
|
224
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import cx from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Menu from './Menu';
|
|
4
|
+
|
|
5
|
+
const BasicToolbar = React.forwardRef(({ className, ...props }, ref) => {
|
|
6
|
+
return (
|
|
7
|
+
<Menu {...props} ref={ref} className={cx(className, 'slate-toolbar')} />
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default BasicToolbar;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSlate } from 'slate-react';
|
|
3
|
+
import { isBlockActive, toggleBlock } from '@plone/volto-slate/utils';
|
|
4
|
+
|
|
5
|
+
import ToolbarButton from './ToolbarButton';
|
|
6
|
+
|
|
7
|
+
const BlockButton = ({ format, icon, allowedChildren, ...props }) => {
|
|
8
|
+
const editor = useSlate();
|
|
9
|
+
|
|
10
|
+
const isActive = isBlockActive(editor, format);
|
|
11
|
+
|
|
12
|
+
const handleMouseDown = React.useCallback(
|
|
13
|
+
(event) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
toggleBlock(editor, format, allowedChildren);
|
|
16
|
+
// console.log('toggled', format, editor);
|
|
17
|
+
},
|
|
18
|
+
[editor, format, allowedChildren], // , isActive
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ToolbarButton
|
|
23
|
+
{...props}
|
|
24
|
+
active={isActive}
|
|
25
|
+
onMouseDown={handleMouseDown}
|
|
26
|
+
icon={icon}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default BlockButton;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSlate } from 'slate-react';
|
|
3
|
+
import { clearFormatting } from '@plone/volto-slate/utils';
|
|
4
|
+
|
|
5
|
+
import ToolbarButton from './ToolbarButton';
|
|
6
|
+
|
|
7
|
+
const ClearFormattingButton = ({ icon, ...props }) => {
|
|
8
|
+
const editor = useSlate();
|
|
9
|
+
|
|
10
|
+
const handleMouseDown = React.useCallback(
|
|
11
|
+
(event) => {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
clearFormatting(editor);
|
|
14
|
+
},
|
|
15
|
+
[editor],
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return <ToolbarButton {...props} onMouseDown={handleMouseDown} icon={icon} />;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default ClearFormattingButton;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import BasicToolbar from './BasicToolbar';
|
|
4
|
+
import Expando from './Expando';
|
|
5
|
+
|
|
6
|
+
const ExpandedToolbar = React.forwardRef(
|
|
7
|
+
({ className, toggleButton, children, ...props }, ref) => {
|
|
8
|
+
return (
|
|
9
|
+
<BasicToolbar {...props} className={className} ref={ref}>
|
|
10
|
+
{children}
|
|
11
|
+
<Expando />
|
|
12
|
+
{toggleButton}
|
|
13
|
+
</BasicToolbar>
|
|
14
|
+
);
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default ExpandedToolbar;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react'; // , useState
|
|
2
|
+
import SlateToolbar from './SlateToolbar';
|
|
3
|
+
import SlateContextToolbar from './SlateContextToolbar';
|
|
4
|
+
import config from '@plone/volto/registry';
|
|
5
|
+
import { hasRangeSelection } from '@plone/volto-slate/utils';
|
|
6
|
+
import { ReactEditor } from 'slate-react';
|
|
7
|
+
import cx from 'classnames';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The main Slate toolbar. All the others are just wrappers, UI or used here
|
|
11
|
+
*/
|
|
12
|
+
const InlineToolbar = (props) => {
|
|
13
|
+
const { editor, className, showExpandedToolbar, setShowExpandedToolbar } =
|
|
14
|
+
props;
|
|
15
|
+
|
|
16
|
+
const slate = props.slateSettings || config.settings.slate;
|
|
17
|
+
|
|
18
|
+
const [showMainToolbar, setShowMainToolbar] = React.useState(
|
|
19
|
+
!!(editor.selection && hasRangeSelection(editor)),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
let el;
|
|
24
|
+
try {
|
|
25
|
+
el = ReactEditor.toDOMNode(editor, editor);
|
|
26
|
+
} catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const toggleToolbar = () => {
|
|
30
|
+
const selection = window.getSelection();
|
|
31
|
+
const { activeElement } = window.document;
|
|
32
|
+
if (activeElement !== el) return;
|
|
33
|
+
if (!selection.isCollapsed && !showMainToolbar) {
|
|
34
|
+
setShowMainToolbar(true);
|
|
35
|
+
} else if (selection.isCollapsed && showMainToolbar) {
|
|
36
|
+
setShowMainToolbar(false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
window.document.addEventListener('selectionchange', toggleToolbar);
|
|
40
|
+
return () => document.removeEventListener('selectionchange', toggleToolbar);
|
|
41
|
+
}, [editor, showMainToolbar]);
|
|
42
|
+
|
|
43
|
+
const showContextToolbar =
|
|
44
|
+
slate.contextToolbarButtons.map((plug) => plug(editor)).filter((c) => !!c)
|
|
45
|
+
.length > 0;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<SlateToolbar
|
|
50
|
+
className={cx(className, {
|
|
51
|
+
upper: showContextToolbar,
|
|
52
|
+
})}
|
|
53
|
+
selected={true}
|
|
54
|
+
enableExpando={slate.enableExpandedToolbar}
|
|
55
|
+
showExpandedToolbar={showExpandedToolbar}
|
|
56
|
+
setShowExpandedToolbar={setShowExpandedToolbar}
|
|
57
|
+
show={showMainToolbar}
|
|
58
|
+
slateSettings={slate}
|
|
59
|
+
/>
|
|
60
|
+
<SlateContextToolbar
|
|
61
|
+
editor={editor}
|
|
62
|
+
plugins={slate.contextToolbarButtons}
|
|
63
|
+
show={showContextToolbar}
|
|
64
|
+
/>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default InlineToolbar;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSlate } from 'slate-react';
|
|
3
|
+
|
|
4
|
+
import { isMarkActive, toggleMark } from '@plone/volto-slate/utils';
|
|
5
|
+
import ToolbarButton from './ToolbarButton';
|
|
6
|
+
|
|
7
|
+
const MarkButton = ({ format, icon, ...props }) => {
|
|
8
|
+
const editor = useSlate();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<ToolbarButton
|
|
12
|
+
{...props}
|
|
13
|
+
active={isMarkActive(editor, format)}
|
|
14
|
+
onMouseDown={(event) => {
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
toggleMark(editor, format);
|
|
17
|
+
}}
|
|
18
|
+
icon={icon}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default MarkButton;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSlate } from 'slate-react';
|
|
3
|
+
import { isBlockActive, toggleInlineFormat } from '@plone/volto-slate/utils';
|
|
4
|
+
|
|
5
|
+
import ToolbarButton from './ToolbarButton';
|
|
6
|
+
|
|
7
|
+
const MarkElementButton = ({ format, icon, ...props }) => {
|
|
8
|
+
const editor = useSlate();
|
|
9
|
+
|
|
10
|
+
const isActive = isBlockActive(editor, format);
|
|
11
|
+
|
|
12
|
+
const handleMouseDown = React.useCallback(
|
|
13
|
+
(event) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
toggleInlineFormat(editor, format);
|
|
16
|
+
},
|
|
17
|
+
[editor, format], // , isActive
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<ToolbarButton
|
|
22
|
+
{...props}
|
|
23
|
+
active={isActive}
|
|
24
|
+
onMouseDown={handleMouseDown}
|
|
25
|
+
icon={icon}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default MarkElementButton;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import cx from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Button } from 'semantic-ui-react';
|
|
4
|
+
|
|
5
|
+
const Menu = React.forwardRef(({ className, ...props }, ref) => {
|
|
6
|
+
return (
|
|
7
|
+
<div className={cx(className, 'slate-menu')} ref={ref}>
|
|
8
|
+
<Button.Group {...props} />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default Menu;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Portal } from 'react-portal';
|
|
3
|
+
|
|
4
|
+
import BasicToolbar from './BasicToolbar';
|
|
5
|
+
|
|
6
|
+
const PositionedToolbar = ({ toggleButton, className, children, position }) => {
|
|
7
|
+
// TODO: "position" is actually an object like `{ style: {} }`
|
|
8
|
+
// To be renamed as "attributes" or "attrs"
|
|
9
|
+
const ref = React.useRef();
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
const el = ref.current;
|
|
13
|
+
|
|
14
|
+
const { style } = position || {};
|
|
15
|
+
const left = `${Math.max(style.left - el.offsetWidth / 2, 0)}px`;
|
|
16
|
+
const top = `${style.top - el.offsetHeight}px`;
|
|
17
|
+
|
|
18
|
+
el.style.opacity = style.opacity;
|
|
19
|
+
el.style.top = top;
|
|
20
|
+
el.style.left = left;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Portal>
|
|
25
|
+
<BasicToolbar className={`slate-inline-toolbar ${className}`} ref={ref}>
|
|
26
|
+
{children}
|
|
27
|
+
</BasicToolbar>
|
|
28
|
+
</Portal>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default PositionedToolbar;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Toolbar from './Toolbar';
|
|
3
|
+
|
|
4
|
+
// A toolbar that conditionally renders itself based on the presense of
|
|
5
|
+
// children
|
|
6
|
+
export default function SlateContextToolbar({ editor, plugins, show }) {
|
|
7
|
+
if (!show) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const components = plugins.map((plug) => plug(editor)).filter((c) => !!c);
|
|
12
|
+
return components.length ? <Toolbar>{components}</Toolbar> : '';
|
|
13
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the main toolbar, which:
|
|
3
|
+
*
|
|
4
|
+
* - appears only when a range selection exists
|
|
5
|
+
* - can be toggled between expanded and hovering state
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import cx from 'classnames';
|
|
11
|
+
|
|
12
|
+
import toggleIcon from '@plone/volto/icons/more.svg';
|
|
13
|
+
|
|
14
|
+
import Toolbar from './Toolbar';
|
|
15
|
+
import ExpandedToolbar from './ExpandedToolbar';
|
|
16
|
+
import ToolbarButton from './ToolbarButton';
|
|
17
|
+
|
|
18
|
+
import config from '@plone/volto/registry';
|
|
19
|
+
|
|
20
|
+
const SlateToolbar = (props) => {
|
|
21
|
+
const {
|
|
22
|
+
selected,
|
|
23
|
+
showExpandedToolbar,
|
|
24
|
+
setShowExpandedToolbar,
|
|
25
|
+
className,
|
|
26
|
+
enableExpando = false,
|
|
27
|
+
show,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const slate = props.slateSettings || config.settings.slate;
|
|
31
|
+
|
|
32
|
+
const { toolbarButtons, expandedToolbarButtons, buttons } = slate;
|
|
33
|
+
|
|
34
|
+
function renderButton(name, index) {
|
|
35
|
+
const Btn = buttons[name];
|
|
36
|
+
if (!Btn) {
|
|
37
|
+
// eslint-disable-next-line
|
|
38
|
+
console.warn('Button not found:', name);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// using also name because some buttons can be like "Separator"
|
|
42
|
+
return <Btn key={`${name}-${index}`} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
{!showExpandedToolbar && (
|
|
48
|
+
<Toolbar
|
|
49
|
+
show={show}
|
|
50
|
+
toggleButton={
|
|
51
|
+
enableExpando && (
|
|
52
|
+
<ToolbarButton
|
|
53
|
+
title="More..."
|
|
54
|
+
onMouseDown={(event) => {
|
|
55
|
+
setShowExpandedToolbar(!showExpandedToolbar);
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
}}
|
|
58
|
+
icon={toggleIcon}
|
|
59
|
+
active={showExpandedToolbar}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
className={className}
|
|
64
|
+
>
|
|
65
|
+
{toolbarButtons?.map(renderButton)}
|
|
66
|
+
</Toolbar>
|
|
67
|
+
)}
|
|
68
|
+
<div
|
|
69
|
+
className={cx('toolbar-wrapper', {
|
|
70
|
+
active: showExpandedToolbar && selected,
|
|
71
|
+
})}
|
|
72
|
+
>
|
|
73
|
+
{selected && showExpandedToolbar && (
|
|
74
|
+
<ExpandedToolbar
|
|
75
|
+
show={show}
|
|
76
|
+
toggleButton={
|
|
77
|
+
<ToolbarButton
|
|
78
|
+
title="Less..."
|
|
79
|
+
onMouseDown={(event) => {
|
|
80
|
+
setShowExpandedToolbar(!showExpandedToolbar);
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
}}
|
|
83
|
+
icon={toggleIcon}
|
|
84
|
+
active={showExpandedToolbar}
|
|
85
|
+
/>
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
{expandedToolbarButtons?.map(renderButton)}
|
|
89
|
+
</ExpandedToolbar>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default SlateToolbar;
|