@eeacms/volto-cca-policy 0.1.13 → 0.1.14

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.
@@ -0,0 +1,346 @@
1
+ import React from 'react';
2
+ import config from '@plone/volto/registry';
3
+
4
+ import boldIcon from '@plone/volto/icons/bold.svg';
5
+ import codeIcon from '@plone/volto/icons/code.svg';
6
+ import formatClearIcon from '@plone/volto/icons/format-clear.svg';
7
+ import headingIcon from '@plone/volto/icons/heading.svg';
8
+ import italicIcon from '@plone/volto/icons/italic.svg';
9
+ import listBulletIcon from '@plone/volto/icons/list-bullet.svg';
10
+ import listNumberedIcon from '@plone/volto/icons/list-numbered.svg';
11
+ import subheadingIcon from '@plone/volto/icons/subheading.svg';
12
+ import subTextIcon from '@plone/volto/icons/subtext.svg';
13
+ import underlineIcon from '@plone/volto/icons/underline.svg';
14
+ import strikethroughIcon from '@plone/volto/icons/strikethrough.svg';
15
+ import subindexIcon from '@plone/volto/icons/subindex.svg';
16
+ import superindexIcon from '@plone/volto/icons/superindex.svg';
17
+
18
+ import { createEmptyParagraph } from '@plone/volto-slate/utils';
19
+
20
+ import {
21
+ MarkButton,
22
+ MarkElementButton,
23
+ BlockButton,
24
+ ClearFormattingButton,
25
+ Separator,
26
+ Expando,
27
+ } from '@plone/volto-slate/editor/ui';
28
+ import { highlightSelection } from '@plone/volto-slate/editor/decorate'; // highlightByType,
29
+ import {
30
+ insertData,
31
+ isInline,
32
+ withDeleteSelectionOnEnter,
33
+ withDeserializers,
34
+ normalizeNode,
35
+ normalizeExternalData,
36
+ } from '@plone/volto-slate/editor/extensions';
37
+ import {
38
+ // inlineTagDeserializer,
39
+ bTagDeserializer,
40
+ blockTagDeserializer,
41
+ bodyTagDeserializer,
42
+ codeTagDeserializer,
43
+ preTagDeserializer,
44
+ removeTagDeserializer,
45
+ spanTagDeserializer,
46
+ } from './deserialize';
47
+
48
+ // Registry of available buttons
49
+ export const buttons = {
50
+ bold: (props) => (
51
+ <MarkElementButton
52
+ title="Bold"
53
+ format="strong"
54
+ icon={boldIcon}
55
+ {...props}
56
+ />
57
+ ),
58
+ italic: (props) => (
59
+ <MarkElementButton
60
+ title="Italic"
61
+ format="em"
62
+ icon={italicIcon}
63
+ {...props}
64
+ />
65
+ ),
66
+ underline: (props) => (
67
+ <MarkElementButton
68
+ title="Underline"
69
+ format="u"
70
+ icon={underlineIcon}
71
+ {...props}
72
+ />
73
+ ),
74
+ strikethrough: (props) => (
75
+ <MarkElementButton
76
+ title="Strikethrough"
77
+ format="del"
78
+ icon={strikethroughIcon}
79
+ {...props}
80
+ />
81
+ ),
82
+ sub: (props) => (
83
+ <MarkElementButton
84
+ title="Subscript"
85
+ format="sub"
86
+ icon={subindexIcon}
87
+ {...props}
88
+ />
89
+ ),
90
+ sup: (props) => (
91
+ <MarkElementButton
92
+ title="Superscript"
93
+ format="sup"
94
+ icon={superindexIcon}
95
+ {...props}
96
+ />
97
+ ),
98
+ code: (props) => (
99
+ <MarkButton title="Code" format="code" icon={codeIcon} {...props} />
100
+ ),
101
+ 'heading-two': (props) => (
102
+ <BlockButton
103
+ title="Title"
104
+ format="h2"
105
+ allowedChildren={config.settings.slate.allowedHeadlineElements}
106
+ icon={headingIcon}
107
+ {...props}
108
+ />
109
+ ),
110
+ 'heading-three': (props) => (
111
+ <BlockButton
112
+ title="Subtitle"
113
+ format="h3"
114
+ allowedChildren={config.settings.slate.allowedHeadlineElements}
115
+ icon={subheadingIcon}
116
+ {...props}
117
+ />
118
+ ),
119
+ 'heading-four': (props) => (
120
+ <BlockButton
121
+ title="Heading 4"
122
+ allowedChildren={config.settings.slate.allowedHeadlineElements}
123
+ format="h4"
124
+ icon={subTextIcon}
125
+ {...props}
126
+ />
127
+ ),
128
+ clearformatting: (props) => (
129
+ <ClearFormattingButton title="Clear formatting" icon={formatClearIcon} />
130
+ ),
131
+ 'numbered-list': (props) => (
132
+ <BlockButton
133
+ title="Numbered list"
134
+ format="ol"
135
+ icon={listNumberedIcon}
136
+ {...props}
137
+ />
138
+ ),
139
+ 'bulleted-list': (props) => (
140
+ <BlockButton title="Bulleted list" format="ul" icon={listBulletIcon} />
141
+ ),
142
+ separator: (props) => <Separator />,
143
+ expando: (props) => <Expando />,
144
+ };
145
+
146
+ export const defaultToolbarButtons = [
147
+ 'bold',
148
+ 'italic',
149
+ 'underline',
150
+ 'strikethrough',
151
+ 'separator',
152
+ 'heading-two',
153
+ 'heading-three',
154
+ 'heading-four',
155
+ 'separator',
156
+ 'clearformatting',
157
+ 'separator',
158
+ 'sub',
159
+ 'sup',
160
+ 'separator',
161
+ 'bulleted-list',
162
+ 'numbered-list',
163
+ ];
164
+
165
+ export const toolbarButtons = [...defaultToolbarButtons];
166
+
167
+ export const expandedToolbarButtons = [...defaultToolbarButtons];
168
+
169
+ // These components are rendered in the toolbar on demand, as configured by
170
+ // plugins. They are rendered as "context" buttons, when there is no selection
171
+ // Each one is a function (editor) => (<Component/> or null). It is important
172
+ // to be able to tell if a plugin would return something because we don't want
173
+ // to render the toolbar at all if there's no children (due to CSS reasons).
174
+ export const contextToolbarButtons = [];
175
+
176
+ // Each Element node type available in the editor can be configured to have
177
+ // specific toolbar buttons shown above the element of that type when it
178
+ // contains the selection. The Table plugin of SlateEditor uses this to put a
179
+ // Delete table button above the currently selected table.
180
+ export const elementToolbarButtons = {};
181
+
182
+ // A set of components that are always rendered, unlike the button variety.
183
+ // They make it possible to orchestrate form-based editing of components
184
+ export const persistentHelpers = [];
185
+
186
+ // The slate editor is "decorated" with the capabilities from this list.
187
+ // While Slate calls them plugins, we use "extension" to avoid confusion.
188
+ // A Volto Slate editor plugins adds more functionality: buttons, new elements,
189
+ // etc.
190
+ // Each extension is a simple mutator function with signature: `editor => editor`.
191
+ // See https://docs.slatejs.org/concepts/07-plugins and
192
+ // https://docs.slatejs.org/concepts/06-editor
193
+ //
194
+ // First here gets executed last, so if you want to override behavior, push new
195
+ // extensions to the end of this list, to rely on default behavior implemented
196
+ // here.
197
+ export const extensions = [
198
+ withDeleteSelectionOnEnter,
199
+ withDeserializers,
200
+ insertData,
201
+ isInline,
202
+ normalizeNode,
203
+ normalizeExternalData,
204
+ ];
205
+
206
+ // Default hotkeys and the format they trigger
207
+ export const hotkeys = {
208
+ 'mod+b': { format: 'strong', type: 'inline' },
209
+ 'mod+i': { format: 'em', type: 'inline' },
210
+ 'mod+u': { format: 'u', type: 'inline' },
211
+ 'mod+s': { format: 'del', type: 'inline' },
212
+ // 'mod+`': { format: 'code', type: 'inline' },
213
+ // TODO: more hotkeys, including from plugins!
214
+ };
215
+
216
+ // Raw shortcut/keydown handlers
217
+ export const keyDownHandlers = {};
218
+
219
+ // Paragraphs (as default type of blocks) and lists need special handling
220
+ export const listTypes = ['ul', 'ol'];
221
+ export const listItemType = 'li';
222
+ export const tableTypes = [
223
+ 'table',
224
+ 'tbody',
225
+ 'thead',
226
+ 'tfoot',
227
+ 'tr',
228
+ 'td',
229
+ 'th',
230
+ ];
231
+ export const defaultBlockType = 'p';
232
+
233
+ // Default rendered elements
234
+ // TODO: expose the IDs in constants.js, for uniformity
235
+ export const elements = {
236
+ default: ({ attributes, children }) => <p {...attributes}>{children}</p>,
237
+
238
+ h1: ({ attributes, children }) => <h1 {...attributes}>{children}</h1>,
239
+ h2: ({ attributes, children }) => <h2 {...attributes}>{children}</h2>,
240
+ h3: ({ attributes, children }) => <h3 {...attributes}>{children}</h3>,
241
+ h4: ({ attributes, children }) => <h4 {...attributes}>{children}</h4>,
242
+
243
+ li: ({ attributes, children }) => <li {...attributes}>{children}</li>,
244
+ ol: ({ attributes, children }) => <ol {...attributes}>{children}</ol>,
245
+ ul: ({ attributes, children }) => {
246
+ return <ul {...attributes}>{children}</ul>;
247
+ },
248
+
249
+ div: ({ attributes, children }) => <div {...attributes}>{children}</div>,
250
+ p: ({ attributes, children, element }) => <p {...attributes}>{children}</p>,
251
+
252
+ // While usual slate editor consider these to be Leafs, we treat them as
253
+ // inline elements because they can sometimes contain elements (ex:
254
+ // <b><a/></b>
255
+ em: ({ children }) => <em>{children}</em>,
256
+ i: ({ children }) => <i>{children}</i>,
257
+ b: ({ children }) => {
258
+ return <b>{children}</b>;
259
+ },
260
+ strong: ({ children }) => {
261
+ return <strong>{children}</strong>;
262
+ },
263
+ u: ({ children }) => <u>{children}</u>,
264
+ s: ({ children }) => <del>{children}</del>,
265
+ del: ({ children }) => <del>{children}</del>,
266
+ sub: ({ children }) => <sub>{children}</sub>,
267
+ sup: ({ children }) => <sup>{children}</sup>,
268
+ code: ({ children }) => <code>{children}</code>,
269
+ };
270
+
271
+ export const inlineElements = [
272
+ 'em',
273
+ 'i',
274
+ 'b',
275
+ 'strong',
276
+ 'u',
277
+ 'del',
278
+ 'sub',
279
+ 'sup',
280
+ 'code',
281
+ ];
282
+
283
+ // Order of definition here is important (higher = inner element)
284
+ export const leafs = {
285
+ // code: ({ children }) => {
286
+ // return <code>{children}</code>;
287
+ // },
288
+ };
289
+
290
+ export const defaultValue = () => {
291
+ return [createEmptyParagraph()];
292
+ };
293
+
294
+ // HTML deserialization (html -> slate data conversion)
295
+ // These are used in clipboard paste handling
296
+ // Any tag that is not listed here (or added by a plugin) will be stripped
297
+ // (its children will be rendered, though)
298
+ export const htmlTagsToSlate = {
299
+ B: bTagDeserializer,
300
+ BODY: bodyTagDeserializer,
301
+ CODE: codeTagDeserializer,
302
+ PRE: preTagDeserializer,
303
+ SPAN: spanTagDeserializer,
304
+
305
+ BLOCKQUOTE: blockTagDeserializer('blockquote'),
306
+ DEL: blockTagDeserializer('del'),
307
+ EM: blockTagDeserializer('em'),
308
+ H1: blockTagDeserializer('h1'),
309
+ H2: blockTagDeserializer('h2'),
310
+ H3: blockTagDeserializer('h3'),
311
+ H4: blockTagDeserializer('h4'),
312
+ H5: blockTagDeserializer('h5'),
313
+ H6: blockTagDeserializer('h6'),
314
+ I: blockTagDeserializer('i'),
315
+ P: blockTagDeserializer('p'),
316
+ S: blockTagDeserializer('del'),
317
+ STRONG: blockTagDeserializer('strong'),
318
+ SUB: blockTagDeserializer('sub'),
319
+ SUP: blockTagDeserializer('sup'),
320
+ U: blockTagDeserializer('u'),
321
+
322
+ OL: blockTagDeserializer('ol'),
323
+ UL: blockTagDeserializer('ul'),
324
+ LI: blockTagDeserializer('li'),
325
+
326
+ COLGROUP: removeTagDeserializer,
327
+ };
328
+
329
+ // Adds "highlight" decoration in the editor. Used by `highlightByType`
330
+ // See the Footnote plugin for an example.
331
+ export const nodeTypesToHighlight = [];
332
+
333
+ // "Runtime" decorator functions. These are transient decorations that are
334
+ // applied in the editor. They are not persisted in the final value, so they
335
+ // are useful for example to highlight search results or a certain type of node
336
+ // Signature: ([node, path], ranges) => ranges
337
+ export const runtimeDecorators = [highlightSelection]; // , highlightByType
338
+
339
+ // Only these types of element nodes are allowed in the headlines
340
+ export const allowedHeadlineElements = ['em', 'i'];
341
+
342
+ // Scroll into view when typing
343
+ export const scrollIntoView = true;
344
+
345
+ // In inline toolbar only one tag should be active at a time.
346
+ export const exclusiveElements = [['sup', 'sub']];
@@ -0,0 +1,195 @@
1
+ import { jsx } from 'slate-hyperscript';
2
+ import { Text } from 'slate';
3
+ // import { isWhitespace } from '@plone/volto-slate/utils';
4
+ import {
5
+ TD,
6
+ TH,
7
+ COMMENT,
8
+ ELEMENT_NODE,
9
+ TEXT_NODE,
10
+ } from '@plone/volto-slate/constants';
11
+
12
+ import { collapseInlineSpace } from '@plone/volto-slate/editor/utils';
13
+
14
+ /**
15
+ * Deserialize to a Slate Node, an Array of Slate Nodes or null
16
+ *
17
+ * One particularity of this function is that it tries to do
18
+ * a "perception-based" conversion. For example, in html, multiple whitespaces
19
+ * display as a single space. A new line character in text is actually rendered
20
+ * as a space, etc. So we try to meet user's expectations that when they
21
+ * copy/paste content, we'll preserve the aspect of their text.
22
+ *
23
+ * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
24
+ */
25
+ export const deserialize = (
26
+ editor,
27
+ el,
28
+ options = { collapseWhitespace: true },
29
+ ) => {
30
+ const { htmlTagsToSlate } = editor;
31
+
32
+ if (el.nodeType === COMMENT) {
33
+ return null;
34
+ } else if (el.nodeType === TEXT_NODE) {
35
+ const text = options.collapseWhitespace
36
+ ? collapseInlineSpace(el)
37
+ : el.textContent;
38
+ return text
39
+ ? {
40
+ text,
41
+ }
42
+ : null;
43
+ } else if (el.nodeType !== ELEMENT_NODE) {
44
+ return null;
45
+ } else if (el.nodeName === 'BR') {
46
+ // gets merged with sibling text nodes by Slate normalization in insertData
47
+ return { text: '\n' };
48
+ }
49
+
50
+ if (el.getAttribute('data-slate-data')) {
51
+ return typeDeserialize(editor, el, options);
52
+ }
53
+
54
+ const { nodeName } = el;
55
+
56
+ if (htmlTagsToSlate[nodeName]) {
57
+ return htmlTagsToSlate[nodeName](editor, el, options);
58
+ }
59
+
60
+ // fallback deserializer, all unknown elements are "stripped"
61
+ return deserializeChildren(el, editor, options);
62
+ };
63
+
64
+ export const typeDeserialize = (editor, el, options) => {
65
+ const jsData = el.getAttribute('data-slate-data');
66
+ const { type, data } = JSON.parse(jsData);
67
+ return jsx(
68
+ 'element',
69
+ { type, data },
70
+ deserializeChildren(el, editor, options),
71
+ );
72
+ };
73
+
74
+ export const deserializeChildren = (parent, editor, options) =>
75
+ Array.from(parent.childNodes)
76
+ .map((el) => deserialize(editor, el, options))
77
+ .flat();
78
+
79
+ export const blockTagDeserializer = (tagname) => (editor, el, options) => {
80
+ // if (tagname === 'h2') debugger;
81
+ let children = deserializeChildren(el, editor, options).filter(
82
+ (n) => n !== null,
83
+ );
84
+
85
+ if (
86
+ [TD, TH].includes(tagname) &&
87
+ children.length > 0 &&
88
+ typeof children[0] === 'string'
89
+ ) {
90
+ // TODO: should here be handled the cases when there are more strings in
91
+ // `children` or when there are besides strings other types of nodes too?
92
+ const p = { type: 'div', children: [{ text: '' }] };
93
+ p.children[0].text = children[0];
94
+ children = [p];
95
+ }
96
+
97
+ // normalizes block elements so that they're never empty
98
+ // Avoids a hard crash from the Slate editor
99
+ const hasValidChildren = children.length && children.find((c) => !!c);
100
+ if (!hasValidChildren) {
101
+ children = [{ text: '' }];
102
+ }
103
+
104
+ return jsx('element', { type: tagname }, children);
105
+ };
106
+
107
+ export const bodyTagDeserializer = (editor, el, options) => {
108
+ return jsx('fragment', {}, deserializeChildren(el, editor, options));
109
+ };
110
+
111
+ export const inlineTagDeserializer = (attrs) => (editor, el, options) => {
112
+ return deserializeChildren(el, editor, options).map((child) => {
113
+ const res =
114
+ Text.isText(child) || typeof child === 'string'
115
+ ? jsx('text', attrs, child)
116
+ : {
117
+ ...child,
118
+ attrs, // pass the inline attrs as separate object
119
+ };
120
+ return res;
121
+ });
122
+ };
123
+
124
+ export const spanTagDeserializer = (editor, el, options) => {
125
+ const style = el.getAttribute('style') || '';
126
+ let children = el.childNodes;
127
+
128
+ if (
129
+ // handle formatting from OpenOffice
130
+ children.length === 1 &&
131
+ children[0].nodeType === TEXT_NODE &&
132
+ children[0].textContent === '\n'
133
+ ) {
134
+ return jsx('text', {}, ' ');
135
+ }
136
+ children = deserializeChildren(el, editor, options);
137
+
138
+ // whitespace is replaced by deserialize() with null;
139
+ children = children.map((c) => (c === null ? '' : c));
140
+
141
+ // TODO: handle sub/sup as <sub> and <sup>
142
+ // Handle Google Docs' <sub> formatting
143
+ if (style.replace(/\s/g, '').indexOf('vertical-align:sub') > -1) {
144
+ const attrs = { sub: true };
145
+ return children.map((child) => {
146
+ return jsx('text', attrs, child);
147
+ });
148
+ }
149
+
150
+ // Handle Google Docs' <sup> formatting
151
+ if (style.replace(/\s/g, '').indexOf('vertical-align:super') > -1) {
152
+ const attrs = { sup: true };
153
+ return children.map((child) => {
154
+ return jsx('text', attrs, child);
155
+ });
156
+ }
157
+
158
+ const res = children.find((c) => typeof c !== 'string')
159
+ ? children
160
+ : jsx('text', {}, children);
161
+
162
+ return res;
163
+ };
164
+
165
+ export const bTagDeserializer = (editor, el, options) => {
166
+ // Google Docs does weird things with <b> tag
167
+ return (el.getAttribute('id') || '').indexOf('docs-internal-guid') > -1
168
+ ? deserializeChildren(el, editor, options)
169
+ : jsx('element', { type: 'b' }, deserializeChildren(el, editor, options));
170
+ };
171
+
172
+ export const codeTagDeserializer = (editor, el, options) => {
173
+ return jsx('element', { type: 'code' }, el.textContent);
174
+ };
175
+
176
+ export const preTagDeserializer = (editor, el, options) => {
177
+ // Based on Slate example implementation. Replaces <pre> tags with <code>.
178
+ // Comment: I don't know how good of an idea is this. I'd rather have two
179
+ // separate formats: "preserve whitespace" and "code". This feels like a hack
180
+ const { nodeName } = el;
181
+ let parent = el;
182
+
183
+ if (el.childNodes[0] && el.childNodes[0].nodeName === 'CODE') {
184
+ parent = el.childNodes[0];
185
+ return codeTagDeserializer(editor, parent, options);
186
+ }
187
+
188
+ return blockTagDeserializer(nodeName)(editor, parent, options);
189
+ };
190
+
191
+ export const removeTagDeserializer = (editor, el, options) => {
192
+ return null;
193
+ };
194
+
195
+ export default deserialize;
@@ -0,0 +1,93 @@
1
+ import { Text, Transforms, Element, Node, Path } from 'slate'; // Editor,
2
+ import config from '@plone/volto/registry';
3
+ import { isEqual } from 'lodash';
4
+
5
+ const hasNoText = (node) => {
6
+ const texts = Array.from(Node.texts(node));
7
+ const text = texts.reduce((acc, [child]) => `${acc}${child?.text || ''}`, '');
8
+ return text === '';
9
+ };
10
+
11
+ export const normalizeNode = (editor) => {
12
+ const { normalizeNode } = editor;
13
+ const { slate } = config.settings;
14
+
15
+ // slate.listTypes is 'ol', 'ul'
16
+ const validListElements = [...slate.listTypes, slate.listItemType];
17
+
18
+ editor.normalizeNode = (entry) => {
19
+ const [node, path] = entry;
20
+
21
+ const isInlineNode = Text.isText(node) || editor.isInline(node);
22
+ const isListTypeNode =
23
+ !isInlineNode &&
24
+ Element.isElement(node) &&
25
+ slate.listTypes.includes(node.type);
26
+
27
+ // delete childless ul/ol nodes
28
+ if (isListTypeNode) {
29
+ if ((node.children || []).length === 0) {
30
+ Transforms.removeNodes(editor, { at: path });
31
+ return;
32
+ }
33
+ }
34
+
35
+ if (node.type === slate.listItemType) {
36
+ // if we have inline text nodes, we lift any found <ul/ol> as sibling
37
+ if (
38
+ node.children?.length &&
39
+ (Text.isText(node.children[0]) || editor.isInline(node.children[0]))
40
+ ) {
41
+ const toLift = Array.from(Node.elements(node))
42
+ .filter(([childNode, childRelPath]) => {
43
+ return (
44
+ childRelPath.length === 1 &&
45
+ slate.listTypes.includes(childNode?.type)
46
+ );
47
+ })
48
+ .map(([n, p]) => [...path, ...p]);
49
+ if (toLift.length) {
50
+ Transforms.liftNodes(editor, {
51
+ at: path,
52
+ split: true,
53
+ match: (childNode, childPath) =>
54
+ Path.isChild(childPath, path) &&
55
+ toLift.findIndex((f) => isEqual(f, childPath)) > -1,
56
+ });
57
+ return;
58
+ }
59
+ }
60
+
61
+ // after we hit enter in a list item, remove any leftover duplicated
62
+ // elements. Slate splits the elements (and copies it to the next line
63
+ const emptyEntries = Array.from(Node.elements(node))
64
+ .filter(([childNode, childRelPath]) => childRelPath.length === 1)
65
+ .filter(([childNode, childRelPath]) => hasNoText(childNode));
66
+
67
+ const [toRemove] = emptyEntries;
68
+ if (toRemove) {
69
+ const [, childRelPath] = toRemove;
70
+ const at = [...path, ...childRelPath];
71
+ Transforms.removeNodes(editor, { at });
72
+ return;
73
+ }
74
+ }
75
+
76
+ if (isListTypeNode) {
77
+ // lift all child nodes of ul/ol that are not ul/ol/li
78
+ for (const [child, childPath] of Node.children(editor, path)) {
79
+ if (
80
+ !validListElements.includes(child.type) &&
81
+ !validListElements.includes(node.type)
82
+ ) {
83
+ Transforms.liftNodes(editor, { at: childPath, split: true });
84
+ return;
85
+ }
86
+ }
87
+ }
88
+
89
+ normalizeNode(entry);
90
+ };
91
+
92
+ return editor;
93
+ };
@@ -0,0 +1,32 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import { Editor, Transforms } from 'slate';
3
+ import { IMAGE } from '@plone/volto-slate/constants';
4
+
5
+ export function syncCreateImageBlock(url) {
6
+ const id = uuid();
7
+ const block = {
8
+ '@type': 'image',
9
+ url,
10
+ };
11
+ return [id, block];
12
+ }
13
+
14
+ // This function is used by deconstructToVoltoBlocks, so not directly by the
15
+ // <SlateEditor>. File exists here because there's no "blocks/Image" folder
16
+ export const extractImages = (editor, pathRef) => {
17
+ const imageNodes = Array.from(
18
+ Editor.nodes(editor, {
19
+ at: pathRef.current,
20
+ match: (node) => node.type === IMAGE,
21
+ }),
22
+ );
23
+ const images = imageNodes.map(([el, path]) => el);
24
+ Transforms.removeNodes(editor, {
25
+ at: pathRef.current,
26
+ match: (node) => node.type === IMAGE,
27
+ });
28
+
29
+ return images.map((el) => syncCreateImageBlock(el.url));
30
+ };
31
+
32
+ extractImages.id = 'extractImages';
@@ -0,0 +1,16 @@
1
+ import { withImage } from '@plone/volto-slate/editor/plugins/Image/extensions';
2
+ import { ImageElement } from '@plone/volto-slate/editor/plugins/Image/render';
3
+ import { extractImages } from './deconstruct';
4
+
5
+ export default function install(config) {
6
+ const { slate } = config.settings;
7
+
8
+ slate.extensions = [...(slate.extensions || []), withImage];
9
+ slate.elements.img = ImageElement;
10
+ slate.voltoBlockEmiters = [
11
+ ...(config.settings.slate.voltoBlockEmiters || []),
12
+ extractImages,
13
+ ];
14
+
15
+ return config;
16
+ }