@contentful/field-editor-rich-text 4.13.12 → 4.14.0
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/dist/cjs/CharConstraints.js +63 -0
- package/dist/cjs/RichTextEditor.js +4 -2
- package/dist/cjs/Toolbar/components/ButtonRedo.js +2 -7
- package/dist/cjs/Toolbar/components/ButtonUndo.js +2 -7
- package/dist/cjs/plugins/CharCounter/index.js +15 -0
- package/dist/cjs/plugins/CharCounter/utils.js +30 -0
- package/dist/cjs/plugins/CharCounter/utils.spec.js +49 -0
- package/dist/cjs/plugins/CharCounter/withCharCounter.js +30 -0
- package/dist/cjs/plugins/Marks/Strikethrough.js +2 -34
- package/dist/cjs/plugins/index.js +2 -0
- package/dist/esm/CharConstraints.js +48 -0
- package/dist/esm/RichTextEditor.js +4 -2
- package/dist/esm/Toolbar/components/ButtonRedo.js +2 -7
- package/dist/esm/Toolbar/components/ButtonUndo.js +2 -7
- package/dist/esm/plugins/CharCounter/index.js +5 -0
- package/dist/esm/plugins/CharCounter/utils.js +20 -0
- package/dist/esm/plugins/CharCounter/utils.spec.js +45 -0
- package/dist/esm/plugins/CharCounter/withCharCounter.js +15 -0
- package/dist/esm/plugins/Marks/Strikethrough.js +2 -34
- package/dist/esm/plugins/index.js +2 -0
- package/dist/types/CharConstraints.d.ts +2 -0
- package/dist/types/RichTextEditor.d.ts +2 -0
- package/dist/types/internal/types/editor.d.ts +1 -1
- package/dist/types/plugins/CharCounter/index.d.ts +2 -0
- package/dist/types/plugins/CharCounter/utils.d.ts +2 -0
- package/dist/types/plugins/CharCounter/utils.spec.d.ts +1 -0
- package/dist/types/plugins/CharCounter/withCharCounter.d.ts +2 -0
- package/package.json +5 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "CharConstraints", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return CharConstraints;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = /*#__PURE__*/ _interop_require_default(require("react"));
|
|
12
|
+
const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
|
|
13
|
+
const _fieldeditorshared = require("@contentful/field-editor-shared");
|
|
14
|
+
const _emotion = require("emotion");
|
|
15
|
+
const _hooks = require("./internal/hooks");
|
|
16
|
+
const _SdkProvider = require("./SdkProvider");
|
|
17
|
+
function _interop_require_default(obj) {
|
|
18
|
+
return obj && obj.__esModule ? obj : {
|
|
19
|
+
default: obj
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const styles = {
|
|
23
|
+
container: (0, _emotion.css)({
|
|
24
|
+
display: 'flex',
|
|
25
|
+
justifyContent: 'space-between',
|
|
26
|
+
fontSize: _f36tokens.default.fontSizeM,
|
|
27
|
+
marginTop: _f36tokens.default.spacingXs,
|
|
28
|
+
color: _f36tokens.default.gray700
|
|
29
|
+
}),
|
|
30
|
+
counterInvalid: (0, _emotion.css)({
|
|
31
|
+
color: _f36tokens.default.red600
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
function CharCounter({ checkConstraints }) {
|
|
35
|
+
const editor = (0, _hooks.usePlateEditorState)();
|
|
36
|
+
const count = editor.getCharacterCount();
|
|
37
|
+
const valid = checkConstraints(count);
|
|
38
|
+
return /*#__PURE__*/ _react.default.createElement("span", {
|
|
39
|
+
className: (0, _emotion.cx)({
|
|
40
|
+
[styles.counterInvalid]: !valid
|
|
41
|
+
})
|
|
42
|
+
}, count, " characters");
|
|
43
|
+
}
|
|
44
|
+
function CharConstraints() {
|
|
45
|
+
const sdk = (0, _SdkProvider.useSdkContext)();
|
|
46
|
+
const { constraints, checkConstraints } = _react.default.useMemo(()=>{
|
|
47
|
+
const constraints = _fieldeditorshared.ConstraintsUtils.fromFieldValidations(sdk.field.validations, 'RichText');
|
|
48
|
+
const checkConstraints = _fieldeditorshared.ConstraintsUtils.makeChecker(constraints);
|
|
49
|
+
return {
|
|
50
|
+
constraints,
|
|
51
|
+
checkConstraints
|
|
52
|
+
};
|
|
53
|
+
}, [
|
|
54
|
+
sdk.field.validations
|
|
55
|
+
]);
|
|
56
|
+
return /*#__PURE__*/ _react.default.createElement("div", {
|
|
57
|
+
className: styles.container
|
|
58
|
+
}, /*#__PURE__*/ _react.default.createElement(CharCounter, {
|
|
59
|
+
checkConstraints: checkConstraints
|
|
60
|
+
}), /*#__PURE__*/ _react.default.createElement(_fieldeditorshared.CharValidation, {
|
|
61
|
+
constraints: constraints
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
@@ -24,6 +24,7 @@ const _platecommon = require("@udecode/plate-common");
|
|
|
24
24
|
const _emotion = require("emotion");
|
|
25
25
|
const _fastdeepequal = /*#__PURE__*/ _interop_require_default(require("fast-deep-equal"));
|
|
26
26
|
const _noop = /*#__PURE__*/ _interop_require_default(require("lodash/noop"));
|
|
27
|
+
const _CharConstraints = require("./CharConstraints");
|
|
27
28
|
const _ContentfulEditorProvider = require("./ContentfulEditorProvider");
|
|
28
29
|
const _editoroverrides = require("./editor-overrides");
|
|
29
30
|
const _toSlateValue = require("./helpers/toSlateValue");
|
|
@@ -130,7 +131,7 @@ const ConnectedRichTextEditor = (props)=>{
|
|
|
130
131
|
className: classNames,
|
|
131
132
|
readOnly: props.isDisabled,
|
|
132
133
|
scrollSelectionIntoView: _editoroverrides.defaultScrollSelectionIntoView
|
|
133
|
-
}))))));
|
|
134
|
+
}), props.withCharValidation && /*#__PURE__*/ _react.createElement(_CharConstraints.CharConstraints, null))))));
|
|
134
135
|
};
|
|
135
136
|
const RichTextEditor = (props)=>{
|
|
136
137
|
const { sdk, isInitiallyDisabled, onAction, restrictedMarks, onChange, isDisabled, ...otherProps } = props;
|
|
@@ -159,7 +160,8 @@ const RichTextEditor = (props)=>{
|
|
|
159
160
|
onAction: onAction,
|
|
160
161
|
isDisabled: disabled,
|
|
161
162
|
onChange: setValue,
|
|
162
|
-
restrictedMarks: restrictedMarks
|
|
163
|
+
restrictedMarks: restrictedMarks,
|
|
164
|
+
withCharValidation: props.withCharValidation ?? true
|
|
163
165
|
}));
|
|
164
166
|
};
|
|
165
167
|
const _default = RichTextEditor;
|
|
@@ -9,8 +9,7 @@ Object.defineProperty(exports, "ButtonRedo", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _react = /*#__PURE__*/ _interop_require_default(require("react"));
|
|
12
|
-
const
|
|
13
|
-
const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
|
|
12
|
+
const _f36icons = require("@contentful/f36-icons");
|
|
14
13
|
const _ContentfulEditorProvider = require("../../ContentfulEditorProvider");
|
|
15
14
|
const _ToolbarButton = require("../../plugins/shared/ToolbarButton");
|
|
16
15
|
function _interop_require_default(obj) {
|
|
@@ -29,9 +28,5 @@ const ButtonRedo = ()=>{
|
|
|
29
28
|
onClick: onClickHandler,
|
|
30
29
|
isActive: false,
|
|
31
30
|
isDisabled: editor.history.redos.length === 0
|
|
32
|
-
}, /*#__PURE__*/ _react.default.createElement(
|
|
33
|
-
color: _f36tokens.default.gray900
|
|
34
|
-
}, /*#__PURE__*/ _react.default.createElement("path", {
|
|
35
|
-
d: "M18.4,10.6C16.55,9 14.15,8 11.5,8C6.85,8 2.92,11.03 1.54,15.22L3.9,16C4.95,12.81 7.95,10.5 11.5,10.5C13.45,10.5 15.23,11.22 16.62,12.38L13,16H22V7L18.4,10.6Z"
|
|
36
|
-
})));
|
|
31
|
+
}, /*#__PURE__*/ _react.default.createElement(_f36icons.ArrowArcRightIcon, null));
|
|
37
32
|
};
|
|
@@ -9,8 +9,7 @@ Object.defineProperty(exports, "ButtonUndo", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _react = /*#__PURE__*/ _interop_require_default(require("react"));
|
|
12
|
-
const
|
|
13
|
-
const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
|
|
12
|
+
const _f36icons = require("@contentful/f36-icons");
|
|
14
13
|
const _ContentfulEditorProvider = require("../../ContentfulEditorProvider");
|
|
15
14
|
const _ToolbarButton = require("../../plugins/shared/ToolbarButton");
|
|
16
15
|
function _interop_require_default(obj) {
|
|
@@ -29,9 +28,5 @@ const ButtonUndo = ()=>{
|
|
|
29
28
|
onClick: onClickHandler,
|
|
30
29
|
isActive: false,
|
|
31
30
|
isDisabled: editor.history.undos.length === 0
|
|
32
|
-
}, /*#__PURE__*/ _react.default.createElement(
|
|
33
|
-
color: _f36tokens.default.gray900
|
|
34
|
-
}, /*#__PURE__*/ _react.default.createElement("path", {
|
|
35
|
-
d: "M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z"
|
|
36
|
-
})));
|
|
31
|
+
}, /*#__PURE__*/ _react.default.createElement(_f36icons.ArrowArcLeftIcon, null));
|
|
37
32
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "createCharCounterPlugin", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return createCharCounterPlugin;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _withCharCounter = require("./withCharCounter");
|
|
12
|
+
const createCharCounterPlugin = ()=>({
|
|
13
|
+
key: 'char-counter',
|
|
14
|
+
withOverrides: _withCharCounter.withCharCounter
|
|
15
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "getTextContent", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return getTextContent;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _internal = require("../../internal");
|
|
12
|
+
const _richtexttypes = require("@contentful/rich-text-types");
|
|
13
|
+
const blocks = new Set(Object.values(_richtexttypes.BLOCKS));
|
|
14
|
+
function getTextContent(root) {
|
|
15
|
+
if ((0, _internal.isText)(root)) {
|
|
16
|
+
return root.text;
|
|
17
|
+
}
|
|
18
|
+
const nodes = root.children;
|
|
19
|
+
return nodes.reduce((acc, node, index)=>{
|
|
20
|
+
const text = getTextContent(node);
|
|
21
|
+
if (!text) {
|
|
22
|
+
return acc;
|
|
23
|
+
}
|
|
24
|
+
const nextNode = nodes[index + 1];
|
|
25
|
+
if (nextNode && blocks.has(nextNode.type)) {
|
|
26
|
+
return acc + text + ' ';
|
|
27
|
+
}
|
|
28
|
+
return acc + text;
|
|
29
|
+
}, '');
|
|
30
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _testutils = require("../../test-utils");
|
|
6
|
+
const _richtextplaintextrenderer = require("@contentful/rich-text-plain-text-renderer");
|
|
7
|
+
const _utils = require("./utils");
|
|
8
|
+
const _contentfulslatejsadapter = require("@contentful/contentful-slatejs-adapter");
|
|
9
|
+
describe('getTextContent', ()=>{
|
|
10
|
+
const cases = [
|
|
11
|
+
{
|
|
12
|
+
title: 'empty document',
|
|
13
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null)
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
title: 'list of paragraphs',
|
|
17
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 1")), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 2")), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 3")))
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'void blocks',
|
|
21
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 1")), /*#__PURE__*/ (0, _testutils.jsx)("hhr", null), /*#__PURE__*/ (0, _testutils.jsx)("hhr", null), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 2")), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 3")))
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'blockquote inside list item',
|
|
25
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null, /*#__PURE__*/ (0, _testutils.jsx)("hul", null, /*#__PURE__*/ (0, _testutils.jsx)("hli", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 1")), /*#__PURE__*/ (0, _testutils.jsx)("hquote", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 2"))))), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "trailing paragraph")))
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'empty blockquote between paragraphs',
|
|
29
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 1")), /*#__PURE__*/ (0, _testutils.jsx)("hquote", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null))), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "paragraph 3")))
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: 'paragraphs with inline nodes',
|
|
33
|
+
input: /*#__PURE__*/ (0, _testutils.jsx)("editor", null, /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "text 1"), /*#__PURE__*/ (0, _testutils.jsx)("hinline", {
|
|
34
|
+
id: "first-entry",
|
|
35
|
+
type: "Entry"
|
|
36
|
+
}), /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "text 2"), /*#__PURE__*/ (0, _testutils.jsx)("hinline", {
|
|
37
|
+
id: "another-entry",
|
|
38
|
+
type: "Entry"
|
|
39
|
+
})), /*#__PURE__*/ (0, _testutils.jsx)("hp", null, /*#__PURE__*/ (0, _testutils.jsx)("htext", null, "text 3")))
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
cases.forEach(({ title, input })=>{
|
|
43
|
+
it(title, ()=>{
|
|
44
|
+
expect((0, _utils.getTextContent)(input)).toBe((0, _richtextplaintextrenderer.documentToPlainTextString)((0, _contentfulslatejsadapter.toContentfulDocument)({
|
|
45
|
+
document: input.children
|
|
46
|
+
})));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "withCharCounter", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return withCharCounter;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _utils = require("./utils");
|
|
12
|
+
const _pdebounce = /*#__PURE__*/ _interop_require_default(require("p-debounce"));
|
|
13
|
+
function _interop_require_default(obj) {
|
|
14
|
+
return obj && obj.__esModule ? obj : {
|
|
15
|
+
default: obj
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const withCharCounter = (editor)=>{
|
|
19
|
+
const { apply } = editor;
|
|
20
|
+
let count = (0, _utils.getTextContent)(editor).length;
|
|
21
|
+
editor.getCharacterCount = ()=>count;
|
|
22
|
+
const recount = (0, _pdebounce.default)(async ()=>{
|
|
23
|
+
count = (0, _utils.getTextContent)(editor).length;
|
|
24
|
+
}, 300);
|
|
25
|
+
editor.apply = (op)=>{
|
|
26
|
+
apply(op);
|
|
27
|
+
recount();
|
|
28
|
+
};
|
|
29
|
+
return editor;
|
|
30
|
+
};
|
|
@@ -23,6 +23,7 @@ _export(exports, {
|
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
|
|
26
|
+
const _f36icons = require("@contentful/f36-icons");
|
|
26
27
|
const _richtexttypes = require("@contentful/rich-text-types");
|
|
27
28
|
const _platebasicmarks = require("@udecode/plate-basic-marks");
|
|
28
29
|
const _emotion = require("emotion");
|
|
@@ -81,40 +82,7 @@ const ToolbarDropdownStrikethroughButton = (0, _MarkToolbarButton.createMarkTool
|
|
|
81
82
|
const ToolbarStrikethroughButton = (0, _MarkToolbarButton.createMarkToolbarButton)({
|
|
82
83
|
title: 'Strikethrough',
|
|
83
84
|
mark: _richtexttypes.MARKS.STRIKETHROUGH,
|
|
84
|
-
icon: /*#__PURE__*/ _react.createElement(
|
|
85
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
86
|
-
viewBox: "0 0 256 256",
|
|
87
|
-
width: "18",
|
|
88
|
-
height: "18"
|
|
89
|
-
}, /*#__PURE__*/ _react.createElement("rect", {
|
|
90
|
-
width: "256",
|
|
91
|
-
height: "256",
|
|
92
|
-
fill: "none"
|
|
93
|
-
}), /*#__PURE__*/ _react.createElement("line", {
|
|
94
|
-
x1: "40",
|
|
95
|
-
y1: "128",
|
|
96
|
-
x2: "216",
|
|
97
|
-
y2: "128",
|
|
98
|
-
fill: "none",
|
|
99
|
-
stroke: "currentColor",
|
|
100
|
-
strokeLinecap: "round",
|
|
101
|
-
strokeLinejoin: "round",
|
|
102
|
-
strokeWidth: "24"
|
|
103
|
-
}), /*#__PURE__*/ _react.createElement("path", {
|
|
104
|
-
d: "M72,168c0,22.09,25.07,40,56,40s56-17.91,56-40c0-23.77-21.62-33-45.6-40",
|
|
105
|
-
fill: "none",
|
|
106
|
-
stroke: "currentColor",
|
|
107
|
-
strokeLinecap: "round",
|
|
108
|
-
strokeLinejoin: "round",
|
|
109
|
-
strokeWidth: "24"
|
|
110
|
-
}), /*#__PURE__*/ _react.createElement("path", {
|
|
111
|
-
d: "M75.11,88c0-22.09,22-40,52.89-40,23,0,40.24,9.87,48,24",
|
|
112
|
-
fill: "none",
|
|
113
|
-
stroke: "currentColor",
|
|
114
|
-
strokeLinecap: "round",
|
|
115
|
-
strokeLinejoin: "round",
|
|
116
|
-
strokeWidth: "24"
|
|
117
|
-
}))
|
|
85
|
+
icon: /*#__PURE__*/ _react.createElement(_f36icons.TextStrikethroughIcon, null)
|
|
118
86
|
});
|
|
119
87
|
function Strikethrough(props) {
|
|
120
88
|
return /*#__PURE__*/ _react.createElement("s", {
|
|
@@ -17,6 +17,7 @@ _export(exports, {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
const _Break = require("./Break");
|
|
20
|
+
const _CharCounter = require("./CharCounter");
|
|
20
21
|
const _CommandPalette = require("./CommandPalette");
|
|
21
22
|
const _useCommands = require("./CommandPalette/useCommands");
|
|
22
23
|
const _DeserializeDocx = require("./DeserializeDocx");
|
|
@@ -65,6 +66,7 @@ const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
|
65
66
|
(0, _Voids.createVoidsPlugin)(),
|
|
66
67
|
(0, _SelectOnBackspace.createSelectOnBackspacePlugin)(),
|
|
67
68
|
(0, _PasteHTML.createPasteHTMLPlugin)(),
|
|
69
|
+
(0, _CharCounter.createCharCounterPlugin)(),
|
|
68
70
|
(0, _Break.createSoftBreakPlugin)(),
|
|
69
71
|
(0, _Break.createExitBreakPlugin)(),
|
|
70
72
|
(0, _Break.createResetNodePlugin)(),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import tokens from '@contentful/f36-tokens';
|
|
3
|
+
import { CharValidation, ConstraintsUtils } from '@contentful/field-editor-shared';
|
|
4
|
+
import { css, cx } from 'emotion';
|
|
5
|
+
import { usePlateEditorState } from './internal/hooks';
|
|
6
|
+
import { useSdkContext } from './SdkProvider';
|
|
7
|
+
const styles = {
|
|
8
|
+
container: css({
|
|
9
|
+
display: 'flex',
|
|
10
|
+
justifyContent: 'space-between',
|
|
11
|
+
fontSize: tokens.fontSizeM,
|
|
12
|
+
marginTop: tokens.spacingXs,
|
|
13
|
+
color: tokens.gray700
|
|
14
|
+
}),
|
|
15
|
+
counterInvalid: css({
|
|
16
|
+
color: tokens.red600
|
|
17
|
+
})
|
|
18
|
+
};
|
|
19
|
+
function CharCounter({ checkConstraints }) {
|
|
20
|
+
const editor = usePlateEditorState();
|
|
21
|
+
const count = editor.getCharacterCount();
|
|
22
|
+
const valid = checkConstraints(count);
|
|
23
|
+
return /*#__PURE__*/ React.createElement("span", {
|
|
24
|
+
className: cx({
|
|
25
|
+
[styles.counterInvalid]: !valid
|
|
26
|
+
})
|
|
27
|
+
}, count, " characters");
|
|
28
|
+
}
|
|
29
|
+
export function CharConstraints() {
|
|
30
|
+
const sdk = useSdkContext();
|
|
31
|
+
const { constraints, checkConstraints } = React.useMemo(()=>{
|
|
32
|
+
const constraints = ConstraintsUtils.fromFieldValidations(sdk.field.validations, 'RichText');
|
|
33
|
+
const checkConstraints = ConstraintsUtils.makeChecker(constraints);
|
|
34
|
+
return {
|
|
35
|
+
constraints,
|
|
36
|
+
checkConstraints
|
|
37
|
+
};
|
|
38
|
+
}, [
|
|
39
|
+
sdk.field.validations
|
|
40
|
+
]);
|
|
41
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
42
|
+
className: styles.container
|
|
43
|
+
}, /*#__PURE__*/ React.createElement(CharCounter, {
|
|
44
|
+
checkConstraints: checkConstraints
|
|
45
|
+
}), /*#__PURE__*/ React.createElement(CharValidation, {
|
|
46
|
+
constraints: constraints
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
@@ -6,6 +6,7 @@ import { PlateContent, Plate } from '@udecode/plate-common';
|
|
|
6
6
|
import { css, cx } from 'emotion';
|
|
7
7
|
import deepEquals from 'fast-deep-equal';
|
|
8
8
|
import noop from 'lodash/noop';
|
|
9
|
+
import { CharConstraints } from './CharConstraints';
|
|
9
10
|
import { ContentfulEditorIdProvider, getContentfulEditorId } from './ContentfulEditorProvider';
|
|
10
11
|
import { defaultScrollSelectionIntoView } from './editor-overrides';
|
|
11
12
|
import { toSlateValue } from './helpers/toSlateValue';
|
|
@@ -66,7 +67,7 @@ export const ConnectedRichTextEditor = (props)=>{
|
|
|
66
67
|
className: classNames,
|
|
67
68
|
readOnly: props.isDisabled,
|
|
68
69
|
scrollSelectionIntoView: defaultScrollSelectionIntoView
|
|
69
|
-
}))))));
|
|
70
|
+
}), props.withCharValidation && /*#__PURE__*/ React.createElement(CharConstraints, null))))));
|
|
70
71
|
};
|
|
71
72
|
const RichTextEditor = (props)=>{
|
|
72
73
|
const { sdk, isInitiallyDisabled, onAction, restrictedMarks, onChange, isDisabled, ...otherProps } = props;
|
|
@@ -95,7 +96,8 @@ const RichTextEditor = (props)=>{
|
|
|
95
96
|
onAction: onAction,
|
|
96
97
|
isDisabled: disabled,
|
|
97
98
|
onChange: setValue,
|
|
98
|
-
restrictedMarks: restrictedMarks
|
|
99
|
+
restrictedMarks: restrictedMarks,
|
|
100
|
+
withCharValidation: props.withCharValidation ?? true
|
|
99
101
|
}));
|
|
100
102
|
};
|
|
101
103
|
export default RichTextEditor;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import tokens from '@contentful/f36-tokens';
|
|
2
|
+
import { ArrowArcRightIcon } from '@contentful/f36-icons';
|
|
4
3
|
import { useContentfulEditor } from '../../ContentfulEditorProvider';
|
|
5
4
|
import { ToolbarButton } from '../../plugins/shared/ToolbarButton';
|
|
6
5
|
export const ButtonRedo = ()=>{
|
|
@@ -14,9 +13,5 @@ export const ButtonRedo = ()=>{
|
|
|
14
13
|
onClick: onClickHandler,
|
|
15
14
|
isActive: false,
|
|
16
15
|
isDisabled: editor.history.redos.length === 0
|
|
17
|
-
}, /*#__PURE__*/ React.createElement(
|
|
18
|
-
color: tokens.gray900
|
|
19
|
-
}, /*#__PURE__*/ React.createElement("path", {
|
|
20
|
-
d: "M18.4,10.6C16.55,9 14.15,8 11.5,8C6.85,8 2.92,11.03 1.54,15.22L3.9,16C4.95,12.81 7.95,10.5 11.5,10.5C13.45,10.5 15.23,11.22 16.62,12.38L13,16H22V7L18.4,10.6Z"
|
|
21
|
-
})));
|
|
16
|
+
}, /*#__PURE__*/ React.createElement(ArrowArcRightIcon, null));
|
|
22
17
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import tokens from '@contentful/f36-tokens';
|
|
2
|
+
import { ArrowArcLeftIcon } from '@contentful/f36-icons';
|
|
4
3
|
import { useContentfulEditor } from '../../ContentfulEditorProvider';
|
|
5
4
|
import { ToolbarButton } from '../../plugins/shared/ToolbarButton';
|
|
6
5
|
export const ButtonUndo = ()=>{
|
|
@@ -14,9 +13,5 @@ export const ButtonUndo = ()=>{
|
|
|
14
13
|
onClick: onClickHandler,
|
|
15
14
|
isActive: false,
|
|
16
15
|
isDisabled: editor.history.undos.length === 0
|
|
17
|
-
}, /*#__PURE__*/ React.createElement(
|
|
18
|
-
color: tokens.gray900
|
|
19
|
-
}, /*#__PURE__*/ React.createElement("path", {
|
|
20
|
-
d: "M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z"
|
|
21
|
-
})));
|
|
16
|
+
}, /*#__PURE__*/ React.createElement(ArrowArcLeftIcon, null));
|
|
22
17
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isText } from '../../internal';
|
|
2
|
+
import { BLOCKS } from '@contentful/rich-text-types';
|
|
3
|
+
const blocks = new Set(Object.values(BLOCKS));
|
|
4
|
+
export function getTextContent(root) {
|
|
5
|
+
if (isText(root)) {
|
|
6
|
+
return root.text;
|
|
7
|
+
}
|
|
8
|
+
const nodes = root.children;
|
|
9
|
+
return nodes.reduce((acc, node, index)=>{
|
|
10
|
+
const text = getTextContent(node);
|
|
11
|
+
if (!text) {
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
|
14
|
+
const nextNode = nodes[index + 1];
|
|
15
|
+
if (nextNode && blocks.has(nextNode.type)) {
|
|
16
|
+
return acc + text + ' ';
|
|
17
|
+
}
|
|
18
|
+
return acc + text;
|
|
19
|
+
}, '');
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx } from '../../test-utils';
|
|
2
|
+
import { documentToPlainTextString } from '@contentful/rich-text-plain-text-renderer';
|
|
3
|
+
import { getTextContent } from './utils';
|
|
4
|
+
import { toContentfulDocument } from '@contentful/contentful-slatejs-adapter';
|
|
5
|
+
describe('getTextContent', ()=>{
|
|
6
|
+
const cases = [
|
|
7
|
+
{
|
|
8
|
+
title: 'empty document',
|
|
9
|
+
input: /*#__PURE__*/ jsx("editor", null)
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
title: 'list of paragraphs',
|
|
13
|
+
input: /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 1")), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 2")), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 3")))
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
title: 'void blocks',
|
|
17
|
+
input: /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 1")), /*#__PURE__*/ jsx("hhr", null), /*#__PURE__*/ jsx("hhr", null), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 2")), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 3")))
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'blockquote inside list item',
|
|
21
|
+
input: /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("hul", null, /*#__PURE__*/ jsx("hli", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 1")), /*#__PURE__*/ jsx("hquote", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 2"))))), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "trailing paragraph")))
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'empty blockquote between paragraphs',
|
|
25
|
+
input: /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 1")), /*#__PURE__*/ jsx("hquote", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null))), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "paragraph 3")))
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'paragraphs with inline nodes',
|
|
29
|
+
input: /*#__PURE__*/ jsx("editor", null, /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "text 1"), /*#__PURE__*/ jsx("hinline", {
|
|
30
|
+
id: "first-entry",
|
|
31
|
+
type: "Entry"
|
|
32
|
+
}), /*#__PURE__*/ jsx("htext", null, "text 2"), /*#__PURE__*/ jsx("hinline", {
|
|
33
|
+
id: "another-entry",
|
|
34
|
+
type: "Entry"
|
|
35
|
+
})), /*#__PURE__*/ jsx("hp", null, /*#__PURE__*/ jsx("htext", null, "text 3")))
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
cases.forEach(({ title, input })=>{
|
|
39
|
+
it(title, ()=>{
|
|
40
|
+
expect(getTextContent(input)).toBe(documentToPlainTextString(toContentfulDocument({
|
|
41
|
+
document: input.children
|
|
42
|
+
})));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getTextContent } from './utils';
|
|
2
|
+
import debounce from 'p-debounce';
|
|
3
|
+
export const withCharCounter = (editor)=>{
|
|
4
|
+
const { apply } = editor;
|
|
5
|
+
let count = getTextContent(editor).length;
|
|
6
|
+
editor.getCharacterCount = ()=>count;
|
|
7
|
+
const recount = debounce(async ()=>{
|
|
8
|
+
count = getTextContent(editor).length;
|
|
9
|
+
}, 300);
|
|
10
|
+
editor.apply = (op)=>{
|
|
11
|
+
apply(op);
|
|
12
|
+
recount();
|
|
13
|
+
};
|
|
14
|
+
return editor;
|
|
15
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { TextStrikethroughIcon } from '@contentful/f36-icons';
|
|
2
3
|
import { MARKS } from '@contentful/rich-text-types';
|
|
3
4
|
import { createStrikethroughPlugin as createDefaultStrikethroughPlugin } from '@udecode/plate-basic-marks';
|
|
4
5
|
import { css } from 'emotion';
|
|
@@ -16,40 +17,7 @@ export const ToolbarDropdownStrikethroughButton = createMarkToolbarButton({
|
|
|
16
17
|
export const ToolbarStrikethroughButton = createMarkToolbarButton({
|
|
17
18
|
title: 'Strikethrough',
|
|
18
19
|
mark: MARKS.STRIKETHROUGH,
|
|
19
|
-
icon: /*#__PURE__*/ React.createElement(
|
|
20
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
21
|
-
viewBox: "0 0 256 256",
|
|
22
|
-
width: "18",
|
|
23
|
-
height: "18"
|
|
24
|
-
}, /*#__PURE__*/ React.createElement("rect", {
|
|
25
|
-
width: "256",
|
|
26
|
-
height: "256",
|
|
27
|
-
fill: "none"
|
|
28
|
-
}), /*#__PURE__*/ React.createElement("line", {
|
|
29
|
-
x1: "40",
|
|
30
|
-
y1: "128",
|
|
31
|
-
x2: "216",
|
|
32
|
-
y2: "128",
|
|
33
|
-
fill: "none",
|
|
34
|
-
stroke: "currentColor",
|
|
35
|
-
strokeLinecap: "round",
|
|
36
|
-
strokeLinejoin: "round",
|
|
37
|
-
strokeWidth: "24"
|
|
38
|
-
}), /*#__PURE__*/ React.createElement("path", {
|
|
39
|
-
d: "M72,168c0,22.09,25.07,40,56,40s56-17.91,56-40c0-23.77-21.62-33-45.6-40",
|
|
40
|
-
fill: "none",
|
|
41
|
-
stroke: "currentColor",
|
|
42
|
-
strokeLinecap: "round",
|
|
43
|
-
strokeLinejoin: "round",
|
|
44
|
-
strokeWidth: "24"
|
|
45
|
-
}), /*#__PURE__*/ React.createElement("path", {
|
|
46
|
-
d: "M75.11,88c0-22.09,22-40,52.89-40,23,0,40.24,9.87,48,24",
|
|
47
|
-
fill: "none",
|
|
48
|
-
stroke: "currentColor",
|
|
49
|
-
strokeLinecap: "round",
|
|
50
|
-
strokeLinejoin: "round",
|
|
51
|
-
strokeWidth: "24"
|
|
52
|
-
}))
|
|
20
|
+
icon: /*#__PURE__*/ React.createElement(TextStrikethroughIcon, null)
|
|
53
21
|
});
|
|
54
22
|
export function Strikethrough(props) {
|
|
55
23
|
return /*#__PURE__*/ React.createElement("s", {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSoftBreakPlugin, createExitBreakPlugin, createResetNodePlugin } from './Break';
|
|
2
|
+
import { createCharCounterPlugin } from './CharCounter';
|
|
2
3
|
import { createCommandPalettePlugin } from './CommandPalette';
|
|
3
4
|
import { isCommandPromptPluginEnabled } from './CommandPalette/useCommands';
|
|
4
5
|
import { createDeserializeDocxPlugin } from './DeserializeDocx';
|
|
@@ -47,6 +48,7 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
|
47
48
|
createVoidsPlugin(),
|
|
48
49
|
createSelectOnBackspacePlugin(),
|
|
49
50
|
createPasteHTMLPlugin(),
|
|
51
|
+
createCharCounterPlugin(),
|
|
50
52
|
createSoftBreakPlugin(),
|
|
51
53
|
createExitBreakPlugin(),
|
|
52
54
|
createResetNodePlugin(),
|
|
@@ -17,6 +17,7 @@ type RichTextProps = {
|
|
|
17
17
|
* @deprecated Use `sdk.field.onValueChanged` instead
|
|
18
18
|
*/
|
|
19
19
|
onChange?: (doc: Contentful.Document) => unknown;
|
|
20
|
+
withCharValidation?: boolean;
|
|
20
21
|
};
|
|
21
22
|
type ConnectedRichTextProps = {
|
|
22
23
|
sdk: FieldAppSDK;
|
|
@@ -30,6 +31,7 @@ type ConnectedRichTextProps = {
|
|
|
30
31
|
isToolbarHidden?: boolean;
|
|
31
32
|
actionsDisabled?: boolean;
|
|
32
33
|
stickyToolbarOffset?: number;
|
|
34
|
+
withCharValidation?: boolean;
|
|
33
35
|
};
|
|
34
36
|
export declare const ConnectedRichTextEditor: (props: ConnectedRichTextProps) => React.JSX.Element;
|
|
35
37
|
declare const RichTextEditor: (props: RichTextProps) => React.JSX.Element;
|
|
@@ -27,8 +27,8 @@ export interface Element extends p.TElement {
|
|
|
27
27
|
export type Value = Element[];
|
|
28
28
|
export type ReactEditor = p.PlateEditor<Value>;
|
|
29
29
|
export interface PlateEditor extends p.PlateEditor<Value> {
|
|
30
|
+
getCharacterCount: () => number;
|
|
30
31
|
tracking: TrackingPluginActions;
|
|
31
|
-
f: any;
|
|
32
32
|
undo: {
|
|
33
33
|
(): void;
|
|
34
34
|
(source: 'toolbar' | 'shortcut'): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/field-editor-rich-text",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"source": "./src/index.tsx",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@contentful/f36-icons": "^5.4.1",
|
|
45
45
|
"@contentful/f36-tokens": "^5.1.0",
|
|
46
46
|
"@contentful/f36-utils": "^5.1.0",
|
|
47
|
-
"@contentful/field-editor-reference": "^6.13.
|
|
48
|
-
"@contentful/field-editor-shared": "^2.
|
|
47
|
+
"@contentful/field-editor-reference": "^6.13.13",
|
|
48
|
+
"@contentful/field-editor-shared": "^2.14.0",
|
|
49
49
|
"@contentful/rich-text-plain-text-renderer": "^17.0.0",
|
|
50
50
|
"@contentful/rich-text-types": "^17.0.0",
|
|
51
51
|
"@popperjs/core": "^2.11.5",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"fast-deep-equal": "^3.1.3",
|
|
66
66
|
"is-hotkey": "^0.2.0",
|
|
67
67
|
"is-plain-obj": "^3.0.0",
|
|
68
|
+
"p-debounce": "^2.1.0",
|
|
68
69
|
"react-popper": "^2.3.0",
|
|
69
70
|
"slate": "0.94.1",
|
|
70
71
|
"slate-history": "0.100.0",
|
|
@@ -88,5 +89,5 @@
|
|
|
88
89
|
"publishConfig": {
|
|
89
90
|
"registry": "https://npm.pkg.github.com/"
|
|
90
91
|
},
|
|
91
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "5e69589554e4e8e0870f08886848dc13434573ca"
|
|
92
93
|
}
|