@vonaffenfels/contentful-teasermanager 1.2.0 → 1.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vonaffenfels/contentful-teasermanager",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "prepublish": "yarn run build",
@@ -70,7 +70,7 @@
70
70
  "classnames": "^2.3.2",
71
71
  "clean-webpack-plugin": "^3.0.0",
72
72
  "cloudinary": "^1.25.1",
73
- "contentful-management": "^7.17.0",
73
+ "contentful-management": "^11",
74
74
  "copy-webpack-plugin": "^8.1.1",
75
75
  "cross-env": "^7.0.3",
76
76
  "css-loader": "^5.2.4",
@@ -98,9 +98,10 @@
98
98
  },
99
99
  "dependencies": {
100
100
  "@vonaffenfels/slate-editor": "^1.2.0",
101
+ "contentful-resolve-response": "^1.9.2",
101
102
  "webpack": "5.88.2"
102
103
  },
103
- "gitHead": "fe9b4ce59e9b4dfae924c580f3a1247c180f4d9b",
104
+ "gitHead": "1e99cab528da01b06334b55df31f21305222e6b8",
104
105
  "publishConfig": {
105
106
  "access": "public"
106
107
  }
@@ -10,6 +10,7 @@ import {
10
10
  DndContext, useDraggable, useDroppable,
11
11
  PointerSensor, useSensor, useSensors,
12
12
  } from '@dnd-kit/core';
13
+ import contentfulResolveResponse from "contentful-resolve-response";
13
14
  import {
14
15
  useEffect, useState,
15
16
  } from "react";
@@ -52,23 +53,25 @@ export const LogicEditor = (props) => {
52
53
  const LogicEditorInner = ({
53
54
  onSave = () => alert("onSave missing"),
54
55
  sdk,
55
- logicData = {
56
- timepoints: [7, 12, 18],
57
- tags: [],
58
- },
56
+ logicData,
59
57
  selectedTags,
60
58
  setSelectedTags,
61
59
  }) => {
62
60
  const contentfulClient = getContentfulClient();
63
61
  const [loading, setLoading] = useState(false);
64
- const [data, setData] = useState([]);
62
+ const [tags, setTags] = useState([]);
63
+ const [categories, setCategories] = useState([]);
65
64
  const [searchQuery, setSearchQuery] = useState("");
66
65
  const [addTimepoint, setAddTimepoint] = useState(7);
67
66
  const debouncedSearch = useDebounce(searchQuery, 500);
68
67
  const [selectedTimepoints, setSelectedTimepoints] = useState([]);
69
68
  useEffect(() => {
70
- setSelectedTimepoints(logicData?.timepoints || []);
71
- }, [logicData?.timepoints]);
69
+ if (logicData?.timepoints) {
70
+ setSelectedTimepoints(logicData?.timepoints);
71
+ } else {
72
+ setSelectedTimepoints([7, 12, 18]);
73
+ }
74
+ }, [logicData]);
72
75
  const {
73
76
  setNodeRef: setOrNodeRef,
74
77
  isOver: isOrOver,
@@ -102,14 +105,48 @@ const LogicEditorInner = ({
102
105
  "sys.id",
103
106
  ].join(","),
104
107
  };
108
+ const paramsNavigation = {
109
+ limit: 1,
110
+ content_type: "navigation",
111
+ include: 2,
112
+ locale,
113
+ "fields.portal": portal,
114
+ "fields.type": "main",
115
+ };
105
116
 
106
117
  if (debouncedSearch) {
107
118
  params["fields.title[match]"] = debouncedSearch;
108
119
  }
109
120
 
110
- contentfulClient.getEntries(params).then((response) => {
121
+ Promise.all([
122
+ contentfulClient.getEntries(params),
123
+ contentfulClient.getEntries(paramsNavigation).then(async (response) => {
124
+ if (!response?.items?.[0]) {
125
+ return [];
126
+ }
127
+
128
+ const references = await sdk.cma.entry.references({entryId: response.items[0].sys.id}, {include: 7});
129
+ const resolved = contentfulResolveResponse(references);
130
+ const allCategories = {};
131
+
132
+ for (const category of resolved) {
133
+ if (!category?.fields?.children?.de) {
134
+ continue;
135
+ }
136
+
137
+ const children = category.fields.children.de || [];
138
+
139
+ for (const child of children) {
140
+ getAllTagsFromCategory(child, allCategories);
141
+ }
142
+ }
143
+
144
+ return Object.values(allCategories);
145
+ }),
146
+ ]).then(([tags, categories]) => {
111
147
  setLoading(false);
112
- setData(response);
148
+ setTags(tags.items);
149
+ setCategories(categories);
113
150
  });
114
151
  }, [debouncedSearch, portal, locale]);
115
152
 
@@ -117,7 +154,7 @@ const LogicEditorInner = ({
117
154
  const orTags = selectedTags.filter((t) => t.type === "or");
118
155
 
119
156
  return <div className="flex flex-col gap-2 p-4">
120
- <div className="sticky top-0 z-10 flex w-full flex-col gap-4 border-b border-gray-200 bg-white py-4">
157
+ <div className="z-10 flex w-full flex-col gap-4 border-b border-gray-200 bg-white py-4">
121
158
  <div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
122
159
  <SectionHeading style={{marginBottom: 0}}>Gewählte Zeitpunkte:</SectionHeading>
123
160
  <div className="flex flex-row items-center gap-2">
@@ -159,53 +196,55 @@ const LogicEditorInner = ({
159
196
  </div>
160
197
  <div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
161
198
  <SectionHeading style={{marginBottom: 0}}>Gewählte Kategorien & Tags:</SectionHeading>
162
- <div className="grid w-full grid-cols-2 gap-4">
163
- <div>
164
- <SectionHeading style={{marginBottom: 0}}>Oder</SectionHeading>
199
+ <div className="grid grid-cols-2 gap-4">
200
+ <div className="flex flex-col gap-2">
201
+ <div>
202
+ <SectionHeading style={{marginBottom: 0}}>Oder</SectionHeading>
203
+ </div>
204
+ <div
205
+ ref={setOrNodeRef}
206
+ style={{
207
+ minHeight: 36,
208
+ backgroundColor: isOrOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
209
+ }}
210
+ className="flex h-full w-full flex-row flex-wrap gap-2"
211
+ >
212
+ {orTags.map((tag) => {
213
+ return <DraggablePill
214
+ key={tag.id}
215
+ id={tag.id}
216
+ variant="active"
217
+ label={(`${tag.label}`)}
218
+ onClose={() => {
219
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
220
+ }}
221
+ />;
222
+ })}
223
+ </div>
165
224
  </div>
166
- <div>
225
+ <div className="flex flex-col gap-2">
167
226
  <SectionHeading style={{marginBottom: 0}}>Und</SectionHeading>
168
- </div>
169
- <div
170
- ref={setOrNodeRef}
171
- style={{
172
- minHeight: 36,
173
- backgroundColor: isOrOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
174
- }}
175
- className="flex w-full flex-row flex-wrap gap-2"
176
- >
177
- {orTags.map((tag) => {
178
- return <DraggablePill
179
- key={tag.id}
180
- id={tag.id}
181
- variant="active"
182
- label={(`${tag.label}`)}
183
- onClose={() => {
184
- setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
185
- }}
186
- />;
187
- })}
188
- </div>
189
- <div
190
- ref={setAndNodeRef}
191
- style={{
192
- minHeight: 36,
193
- backgroundColor: isAndOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
194
- }}
195
- className="flex w-full flex-row flex-wrap gap-2"
196
- >
197
- {andTags.map((tag) => {
198
- return <DraggablePill
199
- key={tag.id}
200
- id={tag.id}
201
- testId={tag.id}
202
- variant="active"
203
- label={(`${tag.label}`)}
204
- onClose={() => {
205
- setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
206
- }}
207
- />;
208
- })}
227
+ <div
228
+ ref={setAndNodeRef}
229
+ style={{
230
+ minHeight: 36,
231
+ backgroundColor: isAndOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
232
+ }}
233
+ className="flex h-full w-full flex-row flex-wrap gap-2"
234
+ >
235
+ {andTags.map((tag) => {
236
+ return <DraggablePill
237
+ key={tag.id}
238
+ id={tag.id}
239
+ testId={tag.id}
240
+ variant="active"
241
+ label={(`${tag.label}`)}
242
+ onClose={() => {
243
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
244
+ }}
245
+ />;
246
+ })}
247
+ </div>
209
248
  </div>
210
249
  </div>
211
250
  </div>
@@ -217,28 +256,81 @@ const LogicEditorInner = ({
217
256
  />
218
257
  </div>
219
258
  </div>
220
- <div className="grid grid-cols-3 gap-4">
221
- {data?.items?.map((tag) => {
222
- return (
223
- <Checkbox
224
- key={tag.sys.id}
225
- isChecked={!!selectedTags.find((t) => t.id === tag.sys.id)}
226
- onChange={(e) => {
227
- if (e.target.checked) {
228
- setSelectedTags([...selectedTags, {
229
- label: tag.fields.title[locale],
230
- id: tag.sys.id,
231
- type: "or",
232
- }]);
233
- } else {
234
- setSelectedTags(selectedTags.filter((t) => t.id !== tag.sys.id));
235
- }
236
- }}
237
- >
238
- {tag.fields.title[locale]}
239
- </Checkbox>
240
- );
241
- })}
259
+ <div className="grid grid-cols-2 gap-4 overflow-y-auto">
260
+ <div className="flex flex-col gap-4">
261
+ <div>
262
+ <SectionHeading style={{marginBottom: 0}}>Kategorien</SectionHeading>
263
+ </div>
264
+ <div className="grid grid-cols-3 gap-4">
265
+ {categories?.map((category) => {
266
+ return (
267
+ <Checkbox
268
+ key={category.id}
269
+ isChecked={selectedTags.some((t) => category.tags.some((c) => c.id === t.id) || t.id === category.id)}
270
+ onChange={(e) => {
271
+ let newTags = [...selectedTags];
272
+
273
+ if (e.target.checked) {
274
+ newTags.push({
275
+ label: category.title,
276
+ id: category.id,
277
+ type: "or",
278
+ });
279
+ for (const tag of category.tags) {
280
+ if (!selectedTags.find((t) => t.id === tag.id)) {
281
+ newTags.push({
282
+ label: tag.label,
283
+ id: tag.id,
284
+ type: "or",
285
+ });
286
+ }
287
+ }
288
+ } else {
289
+ newTags = newTags.filter((t) => t.id !== category.id);
290
+
291
+ for (const tag of category.tags) {
292
+ newTags = newTags.filter((t) => t.id !== tag.id);
293
+ }
294
+ }
295
+
296
+ setSelectedTags(newTags);
297
+ }}
298
+ >
299
+ {category.title} ({selectedTags.filter((t) =>
300
+ category.tags.some((c) => c.id === t.id) || t.id === category.id).length}/{category.tags.length + 1})
301
+ </Checkbox>
302
+ );
303
+ })}
304
+ </div>
305
+ </div>
306
+ <div className="flex flex-col gap-4">
307
+ <div>
308
+ <SectionHeading style={{marginBottom: 0}}>Tags</SectionHeading>
309
+ </div>
310
+ <div className="grid grid-cols-3 gap-4">
311
+ {tags?.map((tag) => {
312
+ return (
313
+ <Checkbox
314
+ key={tag.sys.id}
315
+ isChecked={!!selectedTags.find((t) => t.id === tag.sys.id)}
316
+ onChange={(e) => {
317
+ if (e.target.checked) {
318
+ setSelectedTags([...selectedTags, {
319
+ label: tag.fields.title[locale],
320
+ id: tag.sys.id,
321
+ type: "or",
322
+ }]);
323
+ } else {
324
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.sys.id));
325
+ }
326
+ }}
327
+ >
328
+ {tag.fields.title[locale]}
329
+ </Checkbox>
330
+ );
331
+ })}
332
+ </div>
333
+ </div>
242
334
  </div>
243
335
  <div className="sticky bottom-0 flex w-full flex-row justify-between gap-2 border-t border-gray-200 bg-white py-4">
244
336
  <Button
@@ -259,6 +351,62 @@ const LogicEditorInner = ({
259
351
  </div>;
260
352
  };
261
353
 
354
+ function getAllTagsFromCategory(categoryNode, allCategories = {}) {
355
+ if (!categoryNode || !categoryNode.fields?.children) {
356
+ return allCategories;
357
+ }
358
+
359
+ const children = categoryNode.fields.children.de || [];
360
+
361
+ allCategories[categoryNode.sys.id] = {
362
+ title: categoryNode.fields.title.de,
363
+ tags: [],
364
+ id: categoryNode.sys.id,
365
+ };
366
+
367
+ if (categoryNode?.fields?.tags?.de) {
368
+ for (const tag of categoryNode.fields.tags.de) {
369
+ if (!tag?.fields?.title) {
370
+ continue;
371
+ }
372
+
373
+ if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
374
+ allCategories[categoryNode.sys.id].tags.push({
375
+ label: tag.fields.title.de,
376
+ id: tag.sys.id,
377
+ });
378
+ }
379
+ }
380
+ }
381
+
382
+ for (const child of children) {
383
+ if (!child || !child.fields) {
384
+ continue;
385
+ }
386
+
387
+ if (child?.fields?.tags?.de) {
388
+ for (const tag of child.fields.tags.de) {
389
+ if (!allCategories[categoryNode.sys.id].tags.find((c) => c.id === tag.sys.id)) {
390
+ if (!tag?.fields?.title) {
391
+ continue;
392
+ }
393
+
394
+ allCategories[categoryNode.sys.id].tags.push({
395
+ label: tag.fields.title.de,
396
+ id: tag.sys.id,
397
+ });
398
+ }
399
+ }
400
+ }
401
+
402
+ getAllTagsFromCategory(child, allCategories);
403
+ }
404
+
405
+ allCategories[categoryNode.sys.id].tags = [...new Set(allCategories[categoryNode.sys.id].tags)];
406
+
407
+ return allCategories;
408
+ }
409
+
262
410
 
263
411
  function DraggablePill({
264
412
  id, ...props
@@ -276,19 +424,21 @@ function DraggablePill({
276
424
  };
277
425
 
278
426
  return (
279
- <Pill
280
- dragHandleComponent={
281
- <DragHandle
282
- label="Reorder item"
283
- variant="transparent"
284
- {...attributes}
285
- {...listeners}
286
- />
287
- }
288
- isDraggable
289
- ref={setNodeRef}
290
- style={style}
291
- {...props}
292
- />
427
+ <div>
428
+ <Pill
429
+ dragHandleComponent={
430
+ <DragHandle
431
+ label="Reorder item"
432
+ variant="transparent"
433
+ {...attributes}
434
+ {...listeners}
435
+ />
436
+ }
437
+ isDraggable
438
+ ref={setNodeRef}
439
+ style={style}
440
+ {...props}
441
+ />
442
+ </div>
293
443
  );
294
444
  }