@vonaffenfels/slate-editor 1.0.1
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/.babelrc +44 -0
- package/README.md +5 -0
- package/componentLoader.js +52 -0
- package/dist/BlockEditor.css +93 -0
- package/dist/BlockEditor.js +2 -0
- package/dist/BlockEditor.js.LICENSE.txt +61 -0
- package/dist/Renderer.js +2 -0
- package/dist/Renderer.js.LICENSE.txt +15 -0
- package/dist/fromHTML.js +1 -0
- package/dist/index.css +93 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +69 -0
- package/dist/toHTML.js +2 -0
- package/dist/toHTML.js.LICENSE.txt +23 -0
- package/dist/toText.js +2 -0
- package/dist/toText.js.LICENSE.txt +23 -0
- package/package.json +79 -0
- package/postcss.config.js +7 -0
- package/scss/demo.scss +142 -0
- package/scss/editor.scss +394 -0
- package/scss/storybook.scss +66 -0
- package/scss/toolbar.scss +160 -0
- package/src/BlockEditor.js +252 -0
- package/src/Blocks/EmptyBlock.js +12 -0
- package/src/Blocks/EmptyWrapper.js +5 -0
- package/src/Blocks/ErrorBoundary.js +41 -0
- package/src/Blocks/LayoutBlock.js +179 -0
- package/src/Blocks/LayoutSlot.js +61 -0
- package/src/Context/StorybookContext.js +7 -0
- package/src/Nodes/Default.js +151 -0
- package/src/Nodes/Element.js +72 -0
- package/src/Nodes/Leaf.js +40 -0
- package/src/Nodes/Storybook.js +170 -0
- package/src/Nodes/StorybookDisplay.js +118 -0
- package/src/Nodes/Text.js +67 -0
- package/src/Renderer.js +41 -0
- package/src/Serializer/Html.js +43 -0
- package/src/Serializer/Serializer.js +318 -0
- package/src/Serializer/Text.js +18 -0
- package/src/Serializer/ads.js +175 -0
- package/src/Serializer/index.js +4 -0
- package/src/SidebarEditor/SidebarEditorField.js +249 -0
- package/src/SidebarEditor.css +90 -0
- package/src/SidebarEditor.js +236 -0
- package/src/Storybook.js +152 -0
- package/src/Toolbar/Align.js +65 -0
- package/src/Toolbar/Block.js +121 -0
- package/src/Toolbar/Element.js +49 -0
- package/src/Toolbar/Formats.js +60 -0
- package/src/Toolbar/Insert.js +29 -0
- package/src/Toolbar/Layout.js +333 -0
- package/src/Toolbar/Link.js +165 -0
- package/src/Toolbar/Toolbar.js +164 -0
- package/src/Tools/Margin.js +52 -0
- package/src/dev/App.js +61 -0
- package/src/dev/draftToSlate.json +3148 -0
- package/src/dev/index.css +3 -0
- package/src/dev/index.html +11 -0
- package/src/dev/index.js +5 -0
- package/src/dev/sampleValue1.json +4295 -0
- package/src/dev/sampleValue2.json +0 -0
- package/src/dev/sampleValueValid.json +411 -0
- package/src/dev/testComponents/TestStory.js +9 -0
- package/src/dev/testComponents/TestStory.stories.js +172 -0
- package/src/dev/testSampleValue.json +747 -0
- package/src/fromHTML.js +5 -0
- package/src/index.js +9 -0
- package/src/plugins/ListItem.js +49 -0
- package/src/plugins/SoftBreak.js +24 -0
- package/src/toHTML.js +7 -0
- package/src/toText.js +7 -0
- package/src/util.js +20 -0
- package/storyLoader.js +46 -0
- package/tailwind.config.js +5 -0
- package/webpack.config.build.js +53 -0
- package/webpack.config.dev.js +61 -0
- package/webpack.config.js +117 -0
package/src/Renderer.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, {memo} from 'react';
|
|
2
|
+
import {StorybookContext} from "./Context/StorybookContext";
|
|
3
|
+
import {Serializer} from "./Serializer";
|
|
4
|
+
|
|
5
|
+
function Renderer({
|
|
6
|
+
value,
|
|
7
|
+
modifyElementFunc,
|
|
8
|
+
specialClassMap,
|
|
9
|
+
elementPropsMap,
|
|
10
|
+
transformAttributes,
|
|
11
|
+
storybookComponentLoader,
|
|
12
|
+
storybookComponentDataLoader,
|
|
13
|
+
fixedPositions,
|
|
14
|
+
fixedPositionTypes,
|
|
15
|
+
adDefinitionDesktop,
|
|
16
|
+
adDefinitionMobile,
|
|
17
|
+
defaultComponentReplacement,
|
|
18
|
+
onStorybookElementClick,
|
|
19
|
+
}) {
|
|
20
|
+
return <Serializer
|
|
21
|
+
elementPropsMap={elementPropsMap}
|
|
22
|
+
specialClassMap={specialClassMap}
|
|
23
|
+
defaultComponentReplacement={defaultComponentReplacement}
|
|
24
|
+
transformAttributes={transformAttributes}
|
|
25
|
+
value={value}
|
|
26
|
+
modifyElementFunc={modifyElementFunc}
|
|
27
|
+
storybookComponentLoader={storybookComponentLoader}
|
|
28
|
+
storybookComponentDataLoader={storybookComponentDataLoader}
|
|
29
|
+
fixedPositions={fixedPositions}
|
|
30
|
+
fixedPositionTypes={fixedPositionTypes}
|
|
31
|
+
adDefinitionDesktop={adDefinitionDesktop}
|
|
32
|
+
adDefinitionMobile={adDefinitionMobile}
|
|
33
|
+
isRenderer={true}
|
|
34
|
+
onStorybookElementClick={onStorybookElementClick}/>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const RendererMemo = memo(Renderer);
|
|
38
|
+
|
|
39
|
+
export default RendererMemo;
|
|
40
|
+
|
|
41
|
+
export {StorybookContext};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import escapeHtml from 'escape-html';
|
|
2
|
+
import {Text} from 'slate';
|
|
3
|
+
|
|
4
|
+
export const HtmlSerializer = (slateValue) => {
|
|
5
|
+
if (!Array.isArray(slateValue)) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const html = slateValue.map(serializeNode).join('');
|
|
10
|
+
|
|
11
|
+
return html;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const serializeNode = (node) => {
|
|
15
|
+
if (Text.isText(node)) {
|
|
16
|
+
return escapeHtml(node.text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const children = (node.children || []).map(n => serializeNode(n)).join('');
|
|
20
|
+
|
|
21
|
+
switch (node.type) {
|
|
22
|
+
case 'blockquote':
|
|
23
|
+
return `<blockquote>${children}</blockquote>`;
|
|
24
|
+
case 'heading':
|
|
25
|
+
return `<h3>${children}</h3>`;
|
|
26
|
+
case 'paragraph':
|
|
27
|
+
return `<p>${children}</p>`;
|
|
28
|
+
case 'unordered-list':
|
|
29
|
+
return `<ul>${children}</ul>`;
|
|
30
|
+
case 'ordered-list':
|
|
31
|
+
return `<ol>${children}</ol>`;
|
|
32
|
+
case 'arrow-list':
|
|
33
|
+
return `<ul>${children}</ul>`;
|
|
34
|
+
case 'list-item':
|
|
35
|
+
return `<li>${children}</li>`;
|
|
36
|
+
case 'link':
|
|
37
|
+
return `<a href="${escapeHtml(node?.attributes?.href)}" target="${node?.attributes?.target || ''}">${children}</a>`;
|
|
38
|
+
case 'storybook':
|
|
39
|
+
return ``;
|
|
40
|
+
default:
|
|
41
|
+
return children;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
isValidElement,
|
|
3
|
+
Fragment,
|
|
4
|
+
} from "react";
|
|
5
|
+
import {Default} from "../Nodes/Default";
|
|
6
|
+
import {Leaf} from "../Nodes/Leaf";
|
|
7
|
+
import {StorybookDisplay} from "../Nodes/StorybookDisplay";
|
|
8
|
+
import classNames from "classnames";
|
|
9
|
+
import {addAdsToValue} from "./ads";
|
|
10
|
+
|
|
11
|
+
function isEmptyNode(node) {
|
|
12
|
+
if (!node.type) {
|
|
13
|
+
if (!String(node.text).trim()) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getTextContent(node, text = "") {
|
|
22
|
+
if (!node) {
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!node.children || !node.children.length) {
|
|
27
|
+
if (!node.text) {
|
|
28
|
+
return text;
|
|
29
|
+
} else {
|
|
30
|
+
text += node.text;
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
return node.children.map(v => getTextContent(v, text)).join("").trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isValidNodeForPlacement(node) {
|
|
40
|
+
return true;
|
|
41
|
+
// this is disabled, elements should just clear, otherwise too many ads are missing because of short intro texts and such ...
|
|
42
|
+
/*
|
|
43
|
+
if (node && node.type === "paragraph") {
|
|
44
|
+
const text = getTextContent(node);
|
|
45
|
+
|
|
46
|
+
if (text.length < 200) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
*/
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function Serializer({
|
|
56
|
+
value = [],
|
|
57
|
+
elementPropsMap,
|
|
58
|
+
specialClassMap,
|
|
59
|
+
transformAttributes,
|
|
60
|
+
storybookComponentLoader,
|
|
61
|
+
storybookComponentDataLoader,
|
|
62
|
+
defaultComponentReplacement,
|
|
63
|
+
modifyElementFunc,
|
|
64
|
+
isRenderer = false,
|
|
65
|
+
fixedPositions = {},
|
|
66
|
+
fixedPositionTypes = {},
|
|
67
|
+
adDefinitionDesktop = [],
|
|
68
|
+
adDefinitionMobile = [],
|
|
69
|
+
}) {
|
|
70
|
+
if (value) {
|
|
71
|
+
value = value.filter((node) => {
|
|
72
|
+
if (!node) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (node.type === "paragraph") {
|
|
77
|
+
if (!node.children || node.children.length === 0) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let children = node.children.filter((v) => !isEmptyNode(v));
|
|
82
|
+
|
|
83
|
+
if (children.length === 0) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getPropsForType = (type, isInSlot = false) => {
|
|
93
|
+
if (!elementPropsMap) {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isInSlot) {
|
|
98
|
+
if (elementPropsMap[type + "-in-slot"]) {
|
|
99
|
+
return elementPropsMap[type + "-in-slot"];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return elementPropsMap[type] || {};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const renderElement = (props, isInSlot = false) => {
|
|
107
|
+
const typeProps = getPropsForType(props.type, isInSlot);
|
|
108
|
+
const Wrapper = typeProps.wrapper || function ({children}) {
|
|
109
|
+
return <>{children}</>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
switch (props.type) {
|
|
113
|
+
case 'storybook': {
|
|
114
|
+
const storyProps = {
|
|
115
|
+
element: {
|
|
116
|
+
...props,
|
|
117
|
+
attributes: {
|
|
118
|
+
...(props.attributes || {}),
|
|
119
|
+
isInSlot: isInSlot,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
attributes: props.attributes,
|
|
123
|
+
storybookComponentLoader: storybookComponentLoader,
|
|
124
|
+
storybookComponentDataLoader: storybookComponentDataLoader,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return <Wrapper><StorybookDisplay {...storyProps} /></Wrapper>;
|
|
128
|
+
}
|
|
129
|
+
case 'layout':
|
|
130
|
+
return <Wrapper>
|
|
131
|
+
<div
|
|
132
|
+
data-cols={props.children.length}
|
|
133
|
+
className={classNames({
|
|
134
|
+
"block-editor-layout": true,
|
|
135
|
+
"md:space-x-4": props.attributes?.spacing,
|
|
136
|
+
"mt-16": props?.attributes?.margin?.top,
|
|
137
|
+
"mr-16": props?.attributes?.margin?.right,
|
|
138
|
+
"mb-16": props?.attributes?.margin?.bottom,
|
|
139
|
+
"ml-16": props?.attributes?.margin?.left,
|
|
140
|
+
[typeProps.classNameSite + " site-width"]: !isInSlot && (props.attributes?.width === "site" || props.attributes?.width === "full"),
|
|
141
|
+
[typeProps.classNameArticle + " article-width"]: !isInSlot && (props.attributes?.width === "article" || !props.attributes?.width),
|
|
142
|
+
})}
|
|
143
|
+
>
|
|
144
|
+
{props.children.map((v, i) => <Fragment key={"layout-pos-" + i}>{serializeNode(v, i, isInSlot)}</Fragment>)}
|
|
145
|
+
</div>
|
|
146
|
+
</Wrapper>;
|
|
147
|
+
case 'layout-slot':
|
|
148
|
+
return <Wrapper>
|
|
149
|
+
<div
|
|
150
|
+
className={classNames({
|
|
151
|
+
["block-editor-layout-" + props.attributes.name]: true,
|
|
152
|
+
"mt-4": props.attributes?.margin?.top,
|
|
153
|
+
"md:mr-4": props.attributes?.margin?.right,
|
|
154
|
+
"mb-4": props.attributes?.margin?.bottom,
|
|
155
|
+
"md:ml-4": props.attributes?.margin?.left,
|
|
156
|
+
})}
|
|
157
|
+
>
|
|
158
|
+
{props.children.map((v, i) => <Fragment key={"layout-slot-" + i}>{serializeNode(v, i, props.attributes.name)}</Fragment>)}
|
|
159
|
+
</div>
|
|
160
|
+
</Wrapper>;
|
|
161
|
+
default: {
|
|
162
|
+
const defaultProps = {
|
|
163
|
+
specialClassMap: specialClassMap,
|
|
164
|
+
elementPropsMap: elementPropsMap,
|
|
165
|
+
element: {
|
|
166
|
+
attributes: props.attributes || {},
|
|
167
|
+
align: props.align,
|
|
168
|
+
type: props.type,
|
|
169
|
+
},
|
|
170
|
+
attributes: props.htmlAttributes || {},
|
|
171
|
+
children: props.children.map((v, i) => <Fragment key={props.type + "-pos-item-" + i}>{serializeNode(v, i, isInSlot)}</Fragment>),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return <Default
|
|
175
|
+
{...defaultProps}
|
|
176
|
+
isInSlot={isInSlot}
|
|
177
|
+
isRenderer={isRenderer}
|
|
178
|
+
transformAttributes={transformAttributes}
|
|
179
|
+
defaultComponentReplacement={defaultComponentReplacement}
|
|
180
|
+
/>;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
function serializeNode(node, i, isInSlot = false) {
|
|
187
|
+
if (isValidElement(node)) {
|
|
188
|
+
return node;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof modifyElementFunc === "function") {
|
|
192
|
+
node = modifyElementFunc(node);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!node.children || !node.children.length) {
|
|
196
|
+
return serializeLeaf(node);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return renderElement(node, isInSlot);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function serializeLeaf(props) {
|
|
203
|
+
return <Leaf leaf={props} isRenderer={true} typeProps={getPropsForType("leaf")}>{props.text || ""}</Leaf>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderValue(value) {
|
|
207
|
+
let processedValue = addAdsToValue(value, adDefinitionDesktop, adDefinitionMobile);
|
|
208
|
+
processedValue = addFixedPositionsToValue(processedValue, fixedPositions, fixedPositionTypes);
|
|
209
|
+
|
|
210
|
+
return <>
|
|
211
|
+
{!!processedValue && processedValue.map((v, i) => {
|
|
212
|
+
return <Fragment key={v.block + "-pos-" + i}>{serializeNode(v, i)}</Fragment>;
|
|
213
|
+
})}
|
|
214
|
+
</>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return renderValue(value);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getFixedPosition(fixedPositions, i) {
|
|
221
|
+
return fixedPositions?.[i] || null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isShortParagraph(node) {
|
|
225
|
+
if (node?.type === "paragraph") {
|
|
226
|
+
let textLength = node.children.map(v => v?.text?.length || 0).reduce((pv, cv) => pv + cv, 0) || 0;
|
|
227
|
+
return textLength <= 60;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function getFixedPositionPlacement(node, prevNode, fixedPosType) {
|
|
234
|
+
// inline has to be before
|
|
235
|
+
if (fixedPosType === "inline") {
|
|
236
|
+
return "before";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let placement = "before";
|
|
240
|
+
|
|
241
|
+
if (node?.type === "paragraph" && prevNode?.type === "heading" || isShortParagraph(prevNode)) {
|
|
242
|
+
placement = "after";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return placement;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isNodeValidForCounter(node, counters = {}) {
|
|
249
|
+
let isValid = true;
|
|
250
|
+
|
|
251
|
+
if (node.type === "paragraph") {
|
|
252
|
+
let lengthCounterId = `paragraph_textlength`;
|
|
253
|
+
|
|
254
|
+
if (!counters[lengthCounterId]) {
|
|
255
|
+
counters[lengthCounterId] = 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let textLength = node.children.map(v => v?.text?.length || 0).reduce((pv, cv) => pv + cv, 0) || 0;
|
|
259
|
+
let isHeadline = textLength <= 60;
|
|
260
|
+
|
|
261
|
+
counters[lengthCounterId] += textLength;
|
|
262
|
+
|
|
263
|
+
isValid = !isHeadline && counters[lengthCounterId] >= 200;
|
|
264
|
+
|
|
265
|
+
if (isValid) {
|
|
266
|
+
counters[lengthCounterId] = 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return isValid;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function addFixedPositionsToValue(
|
|
274
|
+
value, fixedPositions, fixedPositionTypes, counters = {},
|
|
275
|
+
) {
|
|
276
|
+
if (!Array.isArray(value)) {
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let processedValue = [];
|
|
281
|
+
|
|
282
|
+
for (let i = 0; i < value.length; i++) {
|
|
283
|
+
let node = value[i];
|
|
284
|
+
let prevNode = value?.[i - 1];
|
|
285
|
+
|
|
286
|
+
if (!node.type) {
|
|
287
|
+
processedValue.push(node);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!counters[node.type]) {
|
|
291
|
+
counters[node.type] = 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let counterId = `${node.type}_${counters[node.type]}`;
|
|
295
|
+
|
|
296
|
+
// only count this node if its valid to be counted (dont count strong headlines and so on)
|
|
297
|
+
if (isNodeValidForCounter(node, counters)) {
|
|
298
|
+
counters[node.type]++;
|
|
299
|
+
let fixedPosType = fixedPositionTypes?.[counterId];
|
|
300
|
+
let fixedPos = getFixedPosition(fixedPositions, counterId);
|
|
301
|
+
let placement = getFixedPositionPlacement(node, prevNode, fixedPosType);
|
|
302
|
+
|
|
303
|
+
if (fixedPos && placement === "before") {
|
|
304
|
+
processedValue.push(fixedPos);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
processedValue.push(node);
|
|
308
|
+
|
|
309
|
+
if (fixedPos && placement === "after") {
|
|
310
|
+
processedValue.push(fixedPos);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
processedValue.push(node);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return processedValue;
|
|
318
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import escapeHtml from 'escape-html';
|
|
2
|
+
import {Text} from 'slate';
|
|
3
|
+
|
|
4
|
+
export const TextSerializer = (slateValue) => {
|
|
5
|
+
if (!Array.isArray(slateValue)) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return slateValue.map(serializeNode).join('');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const serializeNode = (node) => {
|
|
13
|
+
if (Text.isText(node)) {
|
|
14
|
+
return escapeHtml(node.text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return node.children.map(n => serializeNode(n)).join(' ');
|
|
18
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export const addAdsToValue = (value, adDefinitionDesktop = [], adDefinitionMobile = []) => {
|
|
2
|
+
let extendedValue = [];
|
|
3
|
+
let counters = {
|
|
4
|
+
fullTextCounter: 0,
|
|
5
|
+
currTextCounterDesktop: 0,
|
|
6
|
+
currTextCounterMobile: 0,
|
|
7
|
+
currTextLength: 0,
|
|
8
|
+
currElementsChain: [],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let remainingDesktop = [...adDefinitionDesktop];
|
|
12
|
+
let remainingMobile = [...adDefinitionMobile];
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < value.length; i++) {
|
|
15
|
+
const element = value[i];
|
|
16
|
+
const textLength = getElementTextLength(element);
|
|
17
|
+
const isPlaceable = isAdPlaceable(element, counters);
|
|
18
|
+
|
|
19
|
+
let placementDesktop = "after";
|
|
20
|
+
let placementMobile = "after";
|
|
21
|
+
let placedAdDesktop = null;
|
|
22
|
+
let placedAdMobile = null;
|
|
23
|
+
|
|
24
|
+
counters.fullTextCounter += textLength;
|
|
25
|
+
counters.currTextCounterDesktop += textLength;
|
|
26
|
+
counters.currTextCounterMobile += textLength;
|
|
27
|
+
counters.currTextLength = textLength;
|
|
28
|
+
|
|
29
|
+
if (isPlaceable) {
|
|
30
|
+
const currAd = remainingDesktop.shift();
|
|
31
|
+
const currAdMobile = remainingMobile.shift();
|
|
32
|
+
|
|
33
|
+
counters.currElementsChain.push(element);
|
|
34
|
+
|
|
35
|
+
if (checkAdPlacement(
|
|
36
|
+
currAd, counters, counters.currTextCounterDesktop, element,
|
|
37
|
+
)) {
|
|
38
|
+
placedAdDesktop = currAd;
|
|
39
|
+
if (currAd.reset !== false) {
|
|
40
|
+
counters.currTextCounterDesktop = 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (checkAdPlacement(
|
|
45
|
+
currAdMobile, counters, counters.currTextCounterMobile, element,
|
|
46
|
+
)) {
|
|
47
|
+
placedAdMobile = currAdMobile;
|
|
48
|
+
if (currAdMobile.reset !== false) {
|
|
49
|
+
counters.currTextCounterMobile = 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (placedAdDesktop?.placement) {
|
|
54
|
+
placementDesktop = placedAdDesktop?.placement;
|
|
55
|
+
} else if (placedAdDesktop?.inline) {
|
|
56
|
+
placementDesktop = "before";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (placedAdMobile?.placement) {
|
|
60
|
+
placementMobile = placedAdMobile?.placement;
|
|
61
|
+
} else if (placedAdMobile?.inline) {
|
|
62
|
+
placementMobile = "before";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (placedAdDesktop && placementDesktop === "before") {
|
|
66
|
+
extendedValue.push(placedAdDesktop.component);
|
|
67
|
+
}
|
|
68
|
+
if (placedAdMobile && placementMobile === "before") {
|
|
69
|
+
extendedValue.push(placedAdMobile.component);
|
|
70
|
+
}
|
|
71
|
+
extendedValue = extendedValue.concat(counters.currElementsChain);
|
|
72
|
+
if (placedAdDesktop && placementDesktop === "after") {
|
|
73
|
+
extendedValue.push(placedAdDesktop.component);
|
|
74
|
+
}
|
|
75
|
+
if (placedAdMobile && placementMobile === "after") {
|
|
76
|
+
extendedValue.push(placedAdMobile.component);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
counters.currElementsChain = [];
|
|
80
|
+
|
|
81
|
+
// add the elements back, did not get placed!
|
|
82
|
+
if (!placedAdDesktop && currAd) {
|
|
83
|
+
remainingDesktop.unshift(currAd);
|
|
84
|
+
}
|
|
85
|
+
if (!placedAdMobile && currAdMobile) {
|
|
86
|
+
remainingMobile.unshift(currAdMobile);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
counters.currElementsChain.push(element);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (counters.currElementsChain.length) {
|
|
94
|
+
extendedValue = extendedValue.concat(counters.currElementsChain);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return extendedValue;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* checks if ad is placeable, ads can NOT be placed
|
|
102
|
+
* 1. after headlines
|
|
103
|
+
* 2. after galleries (should be before)
|
|
104
|
+
*
|
|
105
|
+
* @param element
|
|
106
|
+
* @param counters
|
|
107
|
+
*/
|
|
108
|
+
const isAdPlaceable = (element, counters) => {
|
|
109
|
+
const type = element?.type;
|
|
110
|
+
const chain = counters?.currElementsChain || [];
|
|
111
|
+
|
|
112
|
+
if (type === "paragraph") {
|
|
113
|
+
return true;
|
|
114
|
+
} else if (type === "storybook") {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// start of a new chain!
|
|
119
|
+
return false;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const checkAdPlacement = (
|
|
123
|
+
ad, counters, currTextCounter = 0, element = null,
|
|
124
|
+
) => {
|
|
125
|
+
let canBePlaced = false;
|
|
126
|
+
|
|
127
|
+
if (!ad) {
|
|
128
|
+
return canBePlaced;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// can instantly be placed
|
|
132
|
+
if (!ad.space) {
|
|
133
|
+
canBePlaced = true;
|
|
134
|
+
} else if (currTextCounter > ad.space) {
|
|
135
|
+
canBePlaced = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// dont allow inline anywhere BUT paragraphs
|
|
139
|
+
if (ad?.inline && element?.type !== "paragraph") {
|
|
140
|
+
canBePlaced = false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return canBePlaced;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const getElementTextLength = (el) => {
|
|
147
|
+
let textLength = 0;
|
|
148
|
+
|
|
149
|
+
// children
|
|
150
|
+
if (el.children) {
|
|
151
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
152
|
+
const child = el.children[i];
|
|
153
|
+
textLength += getElementTextLength(child);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// generic text
|
|
158
|
+
if (el.text) {
|
|
159
|
+
textLength += parseInt(el?.text?.length || 0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// elements
|
|
163
|
+
if (el.type === "storybook") {
|
|
164
|
+
switch (el?.block) {
|
|
165
|
+
case "Media/Image":
|
|
166
|
+
case "Media/Gallery":
|
|
167
|
+
textLength += 100;
|
|
168
|
+
break;
|
|
169
|
+
default:
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return textLength;
|
|
175
|
+
};
|