@plone/volto 18.0.0-alpha.17 → 18.0.0-alpha.19
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/CHANGELOG.md +48 -2
- package/locales/ca/LC_MESSAGES/volto.po +17 -42
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +15 -40
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +14 -39
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +15 -40
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +15 -40
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +15 -40
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +15 -40
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +15 -40
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +15 -40
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +14 -39
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +15 -40
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +15 -40
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +15 -40
- package/locales/ro.json +1 -1
- package/locales/volto.pot +15 -40
- package/locales/zh_CN/LC_MESSAGES/volto.po +15 -40
- package/locales/zh_CN.json +1 -1
- package/package.json +11 -21
- package/src/components/index.js +0 -6
- package/src/components/manage/Add/Add.jsx +1 -1
- package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +8 -2
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +2 -2
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +33 -5
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx +12 -0
- package/src/components/manage/Controlpanels/Groups/RenderGroups.jsx +22 -11
- package/src/components/manage/Controlpanels/Groups/RenderGroups.test.jsx +21 -0
- package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +30 -21
- package/src/components/manage/Controlpanels/Users/RenderUsers.test.jsx +27 -1
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +29 -7
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +51 -3
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
- package/src/components/manage/Form/Form.jsx +1 -1
- package/src/components/theme/SlotRenderer/SlotRenderer.tsx +8 -1
- package/src/config/Blocks.jsx +63 -67
- package/src/config/Loadables.jsx +0 -22
- package/src/config/Widgets.jsx +0 -2
- package/src/config/index.js +0 -13
- package/src/helpers/User/User.js +29 -0
- package/src/helpers/index.js +6 -1
- package/test-setup-config.js +0 -30
- package/types/components/index.d.ts +0 -6
- package/types/config/Blocks.d.ts +0 -51
- package/types/config/Loadables.d.ts +0 -10
- package/types/config/Widgets.d.ts +0 -2
- package/types/helpers/User/User.d.ts +18 -0
- package/types/helpers/index.d.ts +1 -1
- package/webpack-plugins/webpack-bundle-analyze-plugin.js +1 -1
- package/src/components/manage/AnchorPlugin/components/Link/index.jsx +0 -37
- package/src/components/manage/AnchorPlugin/components/LinkButton/index.jsx +0 -126
- package/src/components/manage/AnchorPlugin/index.jsx +0 -82
- package/src/components/manage/AnchorPlugin/linkStrategy.js +0 -21
- package/src/components/manage/AnchorPlugin/utils/EditorUtils.js +0 -47
- package/src/components/manage/Blocks/HeroImageLeft/Data.jsx +0 -29
- package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +0 -493
- package/src/components/manage/Blocks/HeroImageLeft/Edit.test.jsx +0 -58
- package/src/components/manage/Blocks/HeroImageLeft/View.jsx +0 -37
- package/src/components/manage/Blocks/HeroImageLeft/View.test.jsx +0 -9
- package/src/components/manage/Blocks/HeroImageLeft/schema.js +0 -43
- package/src/components/manage/Blocks/Table/Cell.jsx +0 -206
- package/src/components/manage/Blocks/Table/Cell.test.jsx +0 -19
- package/src/components/manage/Blocks/Table/Edit.jsx +0 -748
- package/src/components/manage/Blocks/Table/Edit.test.jsx +0 -44
- package/src/components/manage/Blocks/Table/Readme.md +0 -5
- package/src/components/manage/Blocks/Table/View.jsx +0 -51
- package/src/components/manage/Blocks/Table/View.test.jsx +0 -41
- package/src/components/manage/Blocks/Text/Edit.jsx +0 -372
- package/src/components/manage/Blocks/Text/Edit.test.jsx +0 -46
- package/src/components/manage/Blocks/Text/Readme.md +0 -5
- package/src/components/manage/Blocks/Text/Schema.jsx +0 -31
- package/src/components/manage/Blocks/Text/View.jsx +0 -26
- package/src/components/manage/Blocks/Text/View.test.jsx +0 -28
- package/src/components/manage/LinkDetectionPlugin/link-detection-plugin.jsx +0 -227
- package/src/components/manage/LinkDetectionPlugin/utils.js +0 -12
- package/src/components/manage/Widgets/WysiwygWidget.jsx +0 -350
- package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +0 -24
- package/src/components/manage/Widgets/WysiwygWidget.test.jsx +0 -37
- package/src/config/RichTextEditor/Blocks.jsx +0 -29
- package/src/config/RichTextEditor/FromHTML.jsx +0 -8
- package/src/config/RichTextEditor/Plugins.jsx +0 -59
- package/src/config/RichTextEditor/Styles.jsx +0 -69
- package/src/config/RichTextEditor/ToHTML.jsx +0 -262
- package/src/config/RichTextEditor/index.js +0 -25
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { RichUtils, Modifier, EditorState, SelectionState } from 'draft-js';
|
|
3
|
-
import {
|
|
4
|
-
isURL,
|
|
5
|
-
getUrlFromString,
|
|
6
|
-
} from '@plone/volto/components/manage/LinkDetectionPlugin/utils';
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
Returns editor state with a link entity created / updated to hold the link @data
|
|
10
|
-
for the range specified by @selection
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export function editorStateSettingLink(editorState, selection, data) {
|
|
14
|
-
const contentState = editorState.getCurrentContent();
|
|
15
|
-
const entityKey = getCurrentLinkEntityKey(editorState);
|
|
16
|
-
|
|
17
|
-
let nextEditorState = editorState;
|
|
18
|
-
|
|
19
|
-
if (!entityKey) {
|
|
20
|
-
const contentStateWithEntity = contentState.createEntity(
|
|
21
|
-
'LINK',
|
|
22
|
-
'MUTABLE',
|
|
23
|
-
data,
|
|
24
|
-
);
|
|
25
|
-
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
|
|
26
|
-
nextEditorState = EditorState.set(editorState, {
|
|
27
|
-
currentContent: contentStateWithEntity,
|
|
28
|
-
});
|
|
29
|
-
nextEditorState = RichUtils.toggleLink(
|
|
30
|
-
nextEditorState,
|
|
31
|
-
selection,
|
|
32
|
-
entityKey,
|
|
33
|
-
);
|
|
34
|
-
} else {
|
|
35
|
-
nextEditorState = EditorState.set(editorState, {
|
|
36
|
-
currentContent: editorState
|
|
37
|
-
.getCurrentContent()
|
|
38
|
-
.replaceEntityData(entityKey, data),
|
|
39
|
-
});
|
|
40
|
-
nextEditorState = EditorState.forceSelection(
|
|
41
|
-
nextEditorState,
|
|
42
|
-
editorState.getSelection(),
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return nextEditorState;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/*
|
|
50
|
-
Returns the entityKey for the link entity the user is currently within.
|
|
51
|
-
*/
|
|
52
|
-
export function getCurrentLinkEntityKey(editorState) {
|
|
53
|
-
const contentState = editorState.getCurrentContent();
|
|
54
|
-
const startKey = editorState.getSelection().getStartKey();
|
|
55
|
-
const startOffset = editorState.getSelection().getStartOffset();
|
|
56
|
-
const block = contentState.getBlockForKey(startKey);
|
|
57
|
-
|
|
58
|
-
const linkKey = block.getEntityAt(
|
|
59
|
-
Math.min(block.getText().length - 1, startOffset),
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
if (linkKey) {
|
|
63
|
-
const linkInstance = contentState.getEntity(linkKey);
|
|
64
|
-
if (linkInstance.getType() === 'LINK') {
|
|
65
|
-
return linkKey;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
Returns the URL for the link entity the user is currently within.
|
|
73
|
-
*/
|
|
74
|
-
export function getCurrentLink(editorState) {
|
|
75
|
-
const entityKey = getCurrentLinkEntityKey(editorState);
|
|
76
|
-
return (
|
|
77
|
-
entityKey &&
|
|
78
|
-
editorState.getCurrentContent().getEntity(entityKey).getData().url
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* LINK COMPONENT */
|
|
83
|
-
const Link = (props) => {
|
|
84
|
-
const data = props.contentState.getEntity(props.entityKey).getData();
|
|
85
|
-
const { url } = data;
|
|
86
|
-
if (!url) {
|
|
87
|
-
return <span>{props.children}</span>;
|
|
88
|
-
}
|
|
89
|
-
return (
|
|
90
|
-
<a href={url} title={url}>
|
|
91
|
-
{props.children}
|
|
92
|
-
</a>
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const findLinkEntities = (contentBlock, callback, contentState) => {
|
|
97
|
-
contentBlock.findEntityRanges((character) => {
|
|
98
|
-
const entityKey = character.getEntity();
|
|
99
|
-
if (!entityKey) return;
|
|
100
|
-
const entity = contentState.getEntity(entityKey);
|
|
101
|
-
return entity.getType() === 'LINK' && entity.getData().url;
|
|
102
|
-
}, callback);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const createLinkDetectionPlugin = () => {
|
|
106
|
-
return {
|
|
107
|
-
decorators: [
|
|
108
|
-
{
|
|
109
|
-
strategy: findLinkEntities,
|
|
110
|
-
component: Link,
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
/* The method is always called when we change the data in the editor. */
|
|
114
|
-
onChange: (editorState) => {
|
|
115
|
-
// Returns the current contents of the editor.
|
|
116
|
-
const contentState = editorState.getCurrentContent();
|
|
117
|
-
|
|
118
|
-
// Returns the current cursor/selection state of the editor.
|
|
119
|
-
const selection = editorState.getSelection();
|
|
120
|
-
|
|
121
|
-
if (!selection || !selection.isCollapsed()) {
|
|
122
|
-
return editorState;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const cursorOffset = selection.getStartOffset();
|
|
126
|
-
const cursorBlockKey = selection.getStartKey();
|
|
127
|
-
const cursorBlock = contentState.getBlockForKey(cursorBlockKey);
|
|
128
|
-
|
|
129
|
-
if (cursorBlock.getType() !== 'unstyled') {
|
|
130
|
-
return editorState;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Step 1: Get the word around the cursor by splitting the current block's text
|
|
134
|
-
const text = cursorBlock.getText();
|
|
135
|
-
const currentWordStart = text.lastIndexOf(' ', cursorOffset) + 1;
|
|
136
|
-
let currentWordEnd = text.indexOf(' ', cursorOffset);
|
|
137
|
-
if (currentWordEnd === -1) {
|
|
138
|
-
currentWordEnd = text.length;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const currentWord = text.substr(
|
|
142
|
-
currentWordStart,
|
|
143
|
-
currentWordEnd - currentWordStart,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const currentWordIsURL = isURL(currentWord);
|
|
147
|
-
|
|
148
|
-
// Step 2: Find the existing LINK entity under the user's cursor
|
|
149
|
-
let currentLinkEntityKey = cursorBlock.getEntityAt(
|
|
150
|
-
Math.min(text.length - 1, cursorOffset),
|
|
151
|
-
);
|
|
152
|
-
const inst =
|
|
153
|
-
currentLinkEntityKey && contentState.getEntity(currentLinkEntityKey);
|
|
154
|
-
if (inst && inst.getType() !== 'LINK') {
|
|
155
|
-
currentLinkEntityKey = '';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (currentLinkEntityKey) {
|
|
159
|
-
// Note: we don't touch link values added / removed "explicitly" via the link
|
|
160
|
-
// toolbar button. This means you can make a link with text that doesn't match the link. Link touched by this plugin, have "autodetected" prop setted to true
|
|
161
|
-
const entityExistingData = contentState
|
|
162
|
-
.getEntity(currentLinkEntityKey)
|
|
163
|
-
.getData();
|
|
164
|
-
if (!entityExistingData.autodetected) {
|
|
165
|
-
return editorState;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (currentWordIsURL) {
|
|
169
|
-
// We are modifying the URL - update the entity to reflect the current text
|
|
170
|
-
const contentState = editorState.getCurrentContent();
|
|
171
|
-
return EditorState.set(editorState, {
|
|
172
|
-
currentContent: contentState.replaceEntityData(
|
|
173
|
-
currentLinkEntityKey,
|
|
174
|
-
{
|
|
175
|
-
autodetected: true,
|
|
176
|
-
url: getUrlFromString(currentWord),
|
|
177
|
-
},
|
|
178
|
-
),
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
// We are no longer in a URL but the entity is still present. Remove it from
|
|
182
|
-
// the current character so the linkifying "ends".
|
|
183
|
-
const entityRange = new SelectionState({
|
|
184
|
-
anchorOffset: currentWordStart - 1,
|
|
185
|
-
anchorKey: cursorBlockKey,
|
|
186
|
-
focusOffset: currentWordStart,
|
|
187
|
-
focusKey: cursorBlockKey,
|
|
188
|
-
isBackward: false,
|
|
189
|
-
hasFocus: true,
|
|
190
|
-
});
|
|
191
|
-
return EditorState.set(editorState, {
|
|
192
|
-
currentContent: Modifier.applyEntity(
|
|
193
|
-
editorState.getCurrentContent(),
|
|
194
|
-
entityRange,
|
|
195
|
-
null,
|
|
196
|
-
),
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// There is no entity beneath the current word, but it looks like a URL. Linkify it!
|
|
202
|
-
if (!currentLinkEntityKey && currentWordIsURL) {
|
|
203
|
-
const entityRange = new SelectionState({
|
|
204
|
-
anchorOffset: currentWordStart,
|
|
205
|
-
anchorKey: cursorBlockKey,
|
|
206
|
-
focusOffset: currentWordEnd,
|
|
207
|
-
focusKey: cursorBlockKey,
|
|
208
|
-
isBackward: false,
|
|
209
|
-
hasFocus: false,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
let newEditorState = editorStateSettingLink(editorState, entityRange, {
|
|
213
|
-
autodetected: true,
|
|
214
|
-
url: getUrlFromString(currentWord),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// reset selection to the initial cursor to avoid selecting the entire links
|
|
218
|
-
newEditorState = EditorState.acceptSelection(newEditorState, selection);
|
|
219
|
-
return newEditorState;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return editorState;
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
export default createLinkDetectionPlugin;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import linkifyIt from 'linkify-it';
|
|
2
|
-
|
|
3
|
-
const linkify = linkifyIt();
|
|
4
|
-
|
|
5
|
-
export const getUrlFromString = (text) => {
|
|
6
|
-
const matchLinkList = linkify.match(text);
|
|
7
|
-
return matchLinkList && matchLinkList[0].url;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const isURL = (text) => {
|
|
11
|
-
return !!linkify.match(text);
|
|
12
|
-
};
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WysiwygWidget container.
|
|
3
|
-
* @module components/manage/WysiwygWidget/WysiwygWidget
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
7
|
-
import ReactDOMServer from 'react-dom/server';
|
|
8
|
-
import PropTypes from 'prop-types';
|
|
9
|
-
import { connect, Provider } from 'react-redux';
|
|
10
|
-
import { compose } from 'redux';
|
|
11
|
-
import redraft from 'redraft';
|
|
12
|
-
import { Form, Label, TextArea } from 'semantic-ui-react';
|
|
13
|
-
import { map } from 'lodash';
|
|
14
|
-
import { defineMessages, injectIntl } from 'react-intl';
|
|
15
|
-
import configureStore from 'redux-mock-store';
|
|
16
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
17
|
-
import config from '@plone/volto/registry';
|
|
18
|
-
|
|
19
|
-
import { FormFieldWrapper } from '@plone/volto/components';
|
|
20
|
-
|
|
21
|
-
import loadable from '@loadable/component';
|
|
22
|
-
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
23
|
-
|
|
24
|
-
const Editor = loadable(() => import('draft-js-plugins-editor'));
|
|
25
|
-
|
|
26
|
-
const messages = defineMessages({
|
|
27
|
-
default: {
|
|
28
|
-
id: 'Default',
|
|
29
|
-
defaultMessage: 'Default',
|
|
30
|
-
},
|
|
31
|
-
idTitle: {
|
|
32
|
-
id: 'Short Name',
|
|
33
|
-
defaultMessage: 'Short Name',
|
|
34
|
-
},
|
|
35
|
-
idDescription: {
|
|
36
|
-
id: 'Used for programmatic access to the fieldset.',
|
|
37
|
-
defaultMessage: 'Used for programmatic access to the fieldset.',
|
|
38
|
-
},
|
|
39
|
-
title: {
|
|
40
|
-
id: 'Title',
|
|
41
|
-
defaultMessage: 'Title',
|
|
42
|
-
},
|
|
43
|
-
description: {
|
|
44
|
-
id: 'Description',
|
|
45
|
-
defaultMessage: 'Description',
|
|
46
|
-
},
|
|
47
|
-
required: {
|
|
48
|
-
id: 'Required',
|
|
49
|
-
defaultMessage: 'Required',
|
|
50
|
-
},
|
|
51
|
-
delete: {
|
|
52
|
-
id: 'Delete',
|
|
53
|
-
defaultMessage: 'Delete',
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* WysiwygWidget HTML richtext editing widget
|
|
59
|
-
*
|
|
60
|
-
* To use it, in schema properties, declare a field like:
|
|
61
|
-
*
|
|
62
|
-
* ```jsx
|
|
63
|
-
* {
|
|
64
|
-
* title: "Rich text",
|
|
65
|
-
* widget: 'richtext',
|
|
66
|
-
* }
|
|
67
|
-
* ```
|
|
68
|
-
*
|
|
69
|
-
*/
|
|
70
|
-
class WysiwygWidgetComponent extends Component {
|
|
71
|
-
/**
|
|
72
|
-
* Property types.
|
|
73
|
-
* @property {Object} propTypes Property types.
|
|
74
|
-
* @static
|
|
75
|
-
*/
|
|
76
|
-
static propTypes = {
|
|
77
|
-
/**
|
|
78
|
-
* Id of the field
|
|
79
|
-
*/
|
|
80
|
-
id: PropTypes.string.isRequired,
|
|
81
|
-
/**
|
|
82
|
-
* Title of the field
|
|
83
|
-
*/
|
|
84
|
-
title: PropTypes.string.isRequired,
|
|
85
|
-
/**
|
|
86
|
-
* Description of the field
|
|
87
|
-
*/
|
|
88
|
-
description: PropTypes.string,
|
|
89
|
-
/**
|
|
90
|
-
* True if field is required
|
|
91
|
-
*/
|
|
92
|
-
required: PropTypes.bool,
|
|
93
|
-
/**
|
|
94
|
-
* Value of the field
|
|
95
|
-
*/
|
|
96
|
-
value: PropTypes.shape({
|
|
97
|
-
/**
|
|
98
|
-
* Content type of the value
|
|
99
|
-
*/
|
|
100
|
-
'content-type': PropTypes.string,
|
|
101
|
-
/**
|
|
102
|
-
* Data of the value
|
|
103
|
-
*/
|
|
104
|
-
data: PropTypes.string,
|
|
105
|
-
/**
|
|
106
|
-
* Encoding of the value
|
|
107
|
-
*/
|
|
108
|
-
encoding: PropTypes.string,
|
|
109
|
-
}),
|
|
110
|
-
/**
|
|
111
|
-
* Placeholder for the editor
|
|
112
|
-
*/
|
|
113
|
-
placeholder: PropTypes.string,
|
|
114
|
-
/**
|
|
115
|
-
* List of error messages
|
|
116
|
-
*/
|
|
117
|
-
error: PropTypes.arrayOf(PropTypes.string),
|
|
118
|
-
/**
|
|
119
|
-
* On change handler
|
|
120
|
-
*/
|
|
121
|
-
onChange: PropTypes.func,
|
|
122
|
-
/**
|
|
123
|
-
* On delete handler
|
|
124
|
-
*/
|
|
125
|
-
onDelete: PropTypes.func,
|
|
126
|
-
/**
|
|
127
|
-
* On edit handler
|
|
128
|
-
*/
|
|
129
|
-
onEdit: PropTypes.func,
|
|
130
|
-
/**
|
|
131
|
-
* Wrapped form component
|
|
132
|
-
*/
|
|
133
|
-
wrapped: PropTypes.bool,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Default properties
|
|
138
|
-
* @property {Object} defaultProps Default properties.
|
|
139
|
-
* @static
|
|
140
|
-
*/
|
|
141
|
-
static defaultProps = {
|
|
142
|
-
description: null,
|
|
143
|
-
required: false,
|
|
144
|
-
value: {
|
|
145
|
-
'content-type': 'text/html',
|
|
146
|
-
data: '',
|
|
147
|
-
encoding: 'utf8',
|
|
148
|
-
},
|
|
149
|
-
error: [],
|
|
150
|
-
onEdit: null,
|
|
151
|
-
onDelete: null,
|
|
152
|
-
onChange: null,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Constructor
|
|
157
|
-
* @method constructor
|
|
158
|
-
* @param {Object} props Component properties
|
|
159
|
-
* @constructs WysiwygWidget
|
|
160
|
-
*/
|
|
161
|
-
constructor(props) {
|
|
162
|
-
super(props);
|
|
163
|
-
|
|
164
|
-
const { stateFromHTML } = props.draftJsImportHtml;
|
|
165
|
-
const { EditorState } = props.draftJs;
|
|
166
|
-
const createInlineToolbarPlugin = props.draftJsInlineToolbarPlugin.default;
|
|
167
|
-
|
|
168
|
-
this.draftConfig = config.settings.richtextEditorSettings(props);
|
|
169
|
-
|
|
170
|
-
if (!__SERVER__) {
|
|
171
|
-
let editorState;
|
|
172
|
-
if (props.value && props.value.data) {
|
|
173
|
-
const contentState = stateFromHTML(props.value.data, {
|
|
174
|
-
customBlockFn: this.draftConfig.FromHTMLCustomBlockFn,
|
|
175
|
-
});
|
|
176
|
-
editorState = EditorState.createWithContent(contentState);
|
|
177
|
-
} else {
|
|
178
|
-
editorState = EditorState.createEmpty();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const inlineToolbarPlugin = createInlineToolbarPlugin({
|
|
182
|
-
structure: this.draftConfig.richTextEditorInlineToolbarButtons,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
this.state = { editorState, inlineToolbarPlugin };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
this.schema = {
|
|
189
|
-
fieldsets: [
|
|
190
|
-
{
|
|
191
|
-
id: 'default',
|
|
192
|
-
title: props.intl.formatMessage(messages.default),
|
|
193
|
-
fields: ['title', 'id', 'description', 'required'],
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
properties: {
|
|
197
|
-
id: {
|
|
198
|
-
type: 'string',
|
|
199
|
-
title: props.intl.formatMessage(messages.idTitle),
|
|
200
|
-
description: props.intl.formatMessage(messages.idDescription),
|
|
201
|
-
},
|
|
202
|
-
title: {
|
|
203
|
-
type: 'string',
|
|
204
|
-
title: props.intl.formatMessage(messages.title),
|
|
205
|
-
},
|
|
206
|
-
description: {
|
|
207
|
-
type: 'string',
|
|
208
|
-
widget: 'textarea',
|
|
209
|
-
title: props.intl.formatMessage(messages.description),
|
|
210
|
-
},
|
|
211
|
-
required: {
|
|
212
|
-
type: 'boolean',
|
|
213
|
-
title: props.intl.formatMessage(messages.required),
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
required: ['id', 'title'],
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
this.onChange = this.onChange.bind(this);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Change handler
|
|
224
|
-
* @method onChange
|
|
225
|
-
* @param {object} editorState Editor state.
|
|
226
|
-
* @returns {undefined}
|
|
227
|
-
*/
|
|
228
|
-
onChange(editorState) {
|
|
229
|
-
const { convertToRaw } = this.props.draftJs;
|
|
230
|
-
this.setState({ editorState });
|
|
231
|
-
const mockStore = configureStore();
|
|
232
|
-
|
|
233
|
-
this.props.onChange(this.props.id, {
|
|
234
|
-
'content-type': this.props.value
|
|
235
|
-
? this.props.value['content-type']
|
|
236
|
-
: 'text/html',
|
|
237
|
-
encoding: this.props.value ? this.props.value.encoding : 'utf8',
|
|
238
|
-
data: ReactDOMServer.renderToStaticMarkup(
|
|
239
|
-
<Provider
|
|
240
|
-
store={mockStore({
|
|
241
|
-
userSession: {
|
|
242
|
-
token: this.props.token,
|
|
243
|
-
},
|
|
244
|
-
})}
|
|
245
|
-
>
|
|
246
|
-
<MemoryRouter>
|
|
247
|
-
{redraft(
|
|
248
|
-
convertToRaw(editorState.getCurrentContent()),
|
|
249
|
-
config.settings.richtextViewSettings.ToHTMLRenderers,
|
|
250
|
-
config.settings.richtextViewSettings.ToHTMLOptions,
|
|
251
|
-
)}
|
|
252
|
-
</MemoryRouter>
|
|
253
|
-
</Provider>,
|
|
254
|
-
),
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Render method.
|
|
260
|
-
* @method render
|
|
261
|
-
* @returns {string} Markup for the component.
|
|
262
|
-
*/
|
|
263
|
-
render() {
|
|
264
|
-
const { id, title, description, required, value, error, fieldSet } =
|
|
265
|
-
this.props;
|
|
266
|
-
|
|
267
|
-
if (__SERVER__) {
|
|
268
|
-
return (
|
|
269
|
-
<Form.Field
|
|
270
|
-
inline
|
|
271
|
-
required={required}
|
|
272
|
-
error={error.length > 0}
|
|
273
|
-
className={description ? 'help' : ''}
|
|
274
|
-
id={`${fieldSet || 'field'}-${id}`}
|
|
275
|
-
>
|
|
276
|
-
<div className="wrapper">
|
|
277
|
-
<label htmlFor={`field-${id}`}>{title}</label>
|
|
278
|
-
<TextArea id={id} name={id} value={value ? value.data : ''} />
|
|
279
|
-
{description && <p className="help">{description}</p>}
|
|
280
|
-
{map(error, (message) => (
|
|
281
|
-
<Label key={message} basic color="red" pointing>
|
|
282
|
-
{message}
|
|
283
|
-
</Label>
|
|
284
|
-
))}
|
|
285
|
-
</div>
|
|
286
|
-
</Form.Field>
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
const { InlineToolbar } = this.state.inlineToolbarPlugin;
|
|
290
|
-
|
|
291
|
-
return (
|
|
292
|
-
<FormFieldWrapper {...this.props} className="wysiwyg">
|
|
293
|
-
<div style={{ boxSizing: 'initial' }}>
|
|
294
|
-
{this.props.onChange ? (
|
|
295
|
-
<>
|
|
296
|
-
<Editor
|
|
297
|
-
id={`field-${id}`}
|
|
298
|
-
readOnly={this.props.isDisabled}
|
|
299
|
-
onChange={this.onChange}
|
|
300
|
-
editorState={this.state.editorState}
|
|
301
|
-
plugins={[
|
|
302
|
-
this.state.inlineToolbarPlugin,
|
|
303
|
-
...this.draftConfig.richTextEditorPlugins,
|
|
304
|
-
]}
|
|
305
|
-
placeholder={this.props.placeholder}
|
|
306
|
-
blockRenderMap={this.draftConfig.extendedBlockRenderMap}
|
|
307
|
-
blockStyleFn={this.draftConfig.blockStyleFn}
|
|
308
|
-
customStyleMap={this.draftConfig.customStyleMap}
|
|
309
|
-
/>
|
|
310
|
-
{this.props.onChange && <InlineToolbar />}
|
|
311
|
-
</>
|
|
312
|
-
) : (
|
|
313
|
-
<div className="DraftEditor-root" />
|
|
314
|
-
)}
|
|
315
|
-
</div>
|
|
316
|
-
</FormFieldWrapper>
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export const WysiwygWidget = compose(
|
|
322
|
-
injectIntl,
|
|
323
|
-
injectLazyLibs([
|
|
324
|
-
'draftJs',
|
|
325
|
-
'draftJsBlockBreakoutPlugin',
|
|
326
|
-
'draftJsCreateBlockStyleButton',
|
|
327
|
-
'draftJsCreateInlineStyleButton',
|
|
328
|
-
'draftJsFilters',
|
|
329
|
-
'draftJsImportHtml',
|
|
330
|
-
'draftJsInlineToolbarPlugin',
|
|
331
|
-
'draftJsLibIsSoftNewlineEvent',
|
|
332
|
-
'immutableLib',
|
|
333
|
-
]),
|
|
334
|
-
connect(
|
|
335
|
-
(state, props) => ({
|
|
336
|
-
token: state.userSession.token,
|
|
337
|
-
}),
|
|
338
|
-
{},
|
|
339
|
-
),
|
|
340
|
-
)(WysiwygWidgetComponent);
|
|
341
|
-
|
|
342
|
-
const Preloader = (props) => {
|
|
343
|
-
const [loaded, setLoaded] = React.useState(false);
|
|
344
|
-
React.useEffect(() => {
|
|
345
|
-
Editor.load().then(() => setLoaded(true));
|
|
346
|
-
}, []);
|
|
347
|
-
return loaded ? <WysiwygWidget {...props} /> : null;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export default Preloader;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import WysiwygWidget from './WysiwygWidget';
|
|
3
|
-
import WidgetStory from './story';
|
|
4
|
-
|
|
5
|
-
export const Wysiwyg = WidgetStory.bind({
|
|
6
|
-
props: { id: 'text', title: 'Rich text' },
|
|
7
|
-
widget: WysiwygWidget,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export default {
|
|
11
|
-
title: 'Edit Widgets/Wysiwyg',
|
|
12
|
-
component: WysiwygWidget,
|
|
13
|
-
decorators: [
|
|
14
|
-
(Story) => (
|
|
15
|
-
<div
|
|
16
|
-
className="ui segment form attached"
|
|
17
|
-
style={{ width: '400px', marginTop: '40px' }}
|
|
18
|
-
>
|
|
19
|
-
<Story />
|
|
20
|
-
</div>
|
|
21
|
-
),
|
|
22
|
-
],
|
|
23
|
-
argTypes: {},
|
|
24
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import renderer from 'react-test-renderer';
|
|
3
|
-
import configureStore from 'redux-mock-store';
|
|
4
|
-
import { Provider } from 'react-intl-redux';
|
|
5
|
-
|
|
6
|
-
import { WysiwygWidget } from './WysiwygWidget';
|
|
7
|
-
|
|
8
|
-
global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
|
|
9
|
-
|
|
10
|
-
const mockStore = configureStore();
|
|
11
|
-
|
|
12
|
-
jest.mock('@plone/volto/helpers/Loadable/Loadable');
|
|
13
|
-
beforeAll(
|
|
14
|
-
async () =>
|
|
15
|
-
await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
test('renders a wysiwyg widget component', () => {
|
|
19
|
-
const store = mockStore({
|
|
20
|
-
intl: {
|
|
21
|
-
locale: 'en',
|
|
22
|
-
messages: {},
|
|
23
|
-
},
|
|
24
|
-
userSession: {
|
|
25
|
-
token:
|
|
26
|
-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU4MjM0MzcyNSwiZnVsbG5hbWUiOm51bGx9.BxCvhI8qrtSYUbuTCJCe5TYo1jw8LXZC3gwd726O0UI',
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const component = renderer.create(
|
|
31
|
-
<Provider store={store}>
|
|
32
|
-
<WysiwygWidget id="my-field" title="My field" onChange={() => {}} />
|
|
33
|
-
</Provider>,
|
|
34
|
-
);
|
|
35
|
-
const json = component.toJSON();
|
|
36
|
-
expect(json).toMatchSnapshot();
|
|
37
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export default function Blocks(props) {
|
|
2
|
-
const { draftJs, immutableLib } = props;
|
|
3
|
-
const { DefaultDraftBlockRenderMap } = draftJs;
|
|
4
|
-
const { Map } = immutableLib;
|
|
5
|
-
|
|
6
|
-
const blockRenderMap = Map({
|
|
7
|
-
callout: {
|
|
8
|
-
element: 'p',
|
|
9
|
-
},
|
|
10
|
-
unstyled: {
|
|
11
|
-
element: 'div',
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const extendedBlockRenderMap =
|
|
16
|
-
DefaultDraftBlockRenderMap.merge(blockRenderMap);
|
|
17
|
-
|
|
18
|
-
const blockStyleFn = (contentBlock) => {
|
|
19
|
-
const type = contentBlock.getType();
|
|
20
|
-
if (type === 'callout') {
|
|
21
|
-
return 'callout';
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const listBlockTypes = ['unordered-list-item', 'ordered-list-item'];
|
|
27
|
-
|
|
28
|
-
return { extendedBlockRenderMap, blockStyleFn, listBlockTypes };
|
|
29
|
-
}
|