multi-content-type-relation 2.1.2 → 2.2.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.
@@ -0,0 +1,470 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { useLocation } from "react-router-dom";
5
+ import { Status, Typography, Td, IconButton, Flex, Box, Table, Thead, Tr, Th, Tbody, Loader, Field, TextInput, DesignSystemProvider } from "@strapi/design-system";
6
+ import { f as fetchMatchingContent, u as useTranslate, g as getContentTypeForUid, a as formatToStrapiField, v as validateCurrentRelations } from "./index-BGSdnZnr.mjs";
7
+ import { useSensors, useSensor, PointerSensor, DndContext, closestCenter } from "@dnd-kit/core";
8
+ import { useSortable, SortableContext, verticalListSortingStrategy, arrayMove } from "@dnd-kit/sortable";
9
+ import { Drag, Eye, Plus, Trash } from "@strapi/icons";
10
+ function useSearchedEntries(keyword, contentTypes, locale) {
11
+ const [loading, setLoading] = useState(false);
12
+ const [results, setResults] = useState([]);
13
+ const [total, setTotal] = useState(0);
14
+ async function fetchEntries() {
15
+ setResults([]);
16
+ setTotal(0);
17
+ if (loading || !keyword) return;
18
+ try {
19
+ const { data, total: total2 } = await fetchMatchingContent(
20
+ keyword,
21
+ contentTypes,
22
+ locale
23
+ );
24
+ setResults(data);
25
+ setTotal(total2);
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ }
30
+ useEffect(() => {
31
+ const timeout = setTimeout(() => fetchEntries(), 500);
32
+ return () => clearTimeout(timeout);
33
+ }, [keyword]);
34
+ return useMemo(
35
+ () => ({
36
+ loading,
37
+ results,
38
+ total
39
+ }),
40
+ [results, total]
41
+ );
42
+ }
43
+ const PublicationState = ({
44
+ isPublished,
45
+ hasDraftAndPublish
46
+ }) => {
47
+ const { translate } = useTranslate();
48
+ const configuration = useMemo(() => {
49
+ const conf = {
50
+ variant: "alternative",
51
+ text: translate("publicationState.na")
52
+ };
53
+ if (hasDraftAndPublish) {
54
+ conf.variant = isPublished ? "success" : "secondary";
55
+ conf.text = isPublished ? translate("publicationState.published") : translate("publicationState.draft");
56
+ }
57
+ return conf;
58
+ }, [isPublished, hasDraftAndPublish, translate]);
59
+ return /* @__PURE__ */ jsx(
60
+ Status,
61
+ {
62
+ showbullet: "false",
63
+ variant: configuration.variant,
64
+ size: "S",
65
+ width: "min-content",
66
+ style: { paddingLeft: 12, paddingRight: 12 },
67
+ children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: `${configuration.variant}700`, children: configuration.text })
68
+ }
69
+ );
70
+ };
71
+ const CSS = /* @__PURE__ */ Object.freeze({
72
+ Translate: {
73
+ toString(transform) {
74
+ if (!transform) {
75
+ return;
76
+ }
77
+ const {
78
+ x,
79
+ y
80
+ } = transform;
81
+ return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)";
82
+ }
83
+ },
84
+ Scale: {
85
+ toString(transform) {
86
+ if (!transform) {
87
+ return;
88
+ }
89
+ const {
90
+ scaleX,
91
+ scaleY
92
+ } = transform;
93
+ return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")";
94
+ }
95
+ },
96
+ Transform: {
97
+ toString(transform) {
98
+ if (!transform) {
99
+ return;
100
+ }
101
+ return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" ");
102
+ }
103
+ },
104
+ Transition: {
105
+ toString(_ref) {
106
+ let {
107
+ property,
108
+ duration,
109
+ easing
110
+ } = _ref;
111
+ return property + " " + duration + "ms " + easing;
112
+ }
113
+ }
114
+ });
115
+ const TableItem = ({
116
+ entry,
117
+ id,
118
+ type,
119
+ disabled,
120
+ onAdd,
121
+ onDelete
122
+ }) => {
123
+ const { translate } = useTranslate();
124
+ const contentType = getContentTypeForUid(entry.uid);
125
+ const location = useLocation();
126
+ const { setDraggableNodeRef, setDroppableNodeRef, transform, transition, attributes, listeners } = useSortable({ id });
127
+ const style = {
128
+ transform: CSS.Transform.toString(transform),
129
+ transition
130
+ };
131
+ const [currentLocale, setCurrentLocale] = useState("");
132
+ useEffect(() => {
133
+ const searchParams = new URLSearchParams(location.search);
134
+ const locale = searchParams.get("plugins[i18n][locale]");
135
+ if (!locale) return;
136
+ setCurrentLocale(locale);
137
+ }, [location]);
138
+ const goToEntry = () => {
139
+ if (!currentLocale) return;
140
+ const contentTypes = window.sessionStorage.getItem("mctr::content_types");
141
+ if (contentTypes) {
142
+ try {
143
+ const parsedContentTypes = JSON.parse(contentTypes);
144
+ if (Array.isArray(parsedContentTypes)) {
145
+ const contentType2 = parsedContentTypes.find(
146
+ (ct) => ct.uid === entry.uid
147
+ );
148
+ if (contentType2) {
149
+ const kind = contentType2.kind;
150
+ let url = "";
151
+ if (kind === "collectionType") {
152
+ url = `/admin/content-manager/collection-types/${entry.uid}/${entry.item.documentId}`;
153
+ } else {
154
+ url = `/admin/content-manager/single-types/${entry.uid}`;
155
+ }
156
+ url += `?plugins[i18n][locale]=${currentLocale}`;
157
+ window.open(url, "_blank");
158
+ return;
159
+ }
160
+ }
161
+ } catch (e) {
162
+ console.error("[MCTR] Failed to retrieve content types");
163
+ }
164
+ } else {
165
+ alert(translate("tableItem.error"));
166
+ }
167
+ };
168
+ return /* @__PURE__ */ jsxs(
169
+ "tr",
170
+ {
171
+ style,
172
+ ref: setDroppableNodeRef,
173
+ ...attributes,
174
+ children: [
175
+ /* @__PURE__ */ jsx(Td, { children: type === "selected" ? /* @__PURE__ */ jsx(IconButton, { ref: setDraggableNodeRef, ...listeners, children: /* @__PURE__ */ jsx(Drag, {}) }) : null }),
176
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { color: "neutral800", children: entry.item[entry.searchableField] }) }),
177
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { color: "neutral800", children: entry.displayName }) }),
178
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
179
+ PublicationState,
180
+ {
181
+ isPublished: !!entry.item.publishedAt,
182
+ hasDraftAndPublish: contentType?.options?.draftAndPublish
183
+ }
184
+ ) }),
185
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { children: [
186
+ /* @__PURE__ */ jsx(
187
+ IconButton,
188
+ {
189
+ label: translate("tableItem.goToEntry"),
190
+ onClick: goToEntry,
191
+ style: { "marginRight": "5px" },
192
+ children: /* @__PURE__ */ jsx(Eye, {})
193
+ }
194
+ ),
195
+ type === "suggestion" ? /* @__PURE__ */ jsx(
196
+ IconButton,
197
+ {
198
+ label: translate("tableItem.add"),
199
+ onClick: () => onAdd(entry),
200
+ disabled,
201
+ marginLeft: 1,
202
+ children: /* @__PURE__ */ jsx(Plus, {})
203
+ }
204
+ ) : type === "selected" ? /* @__PURE__ */ jsx(
205
+ IconButton,
206
+ {
207
+ label: translate("tableItem.delete"),
208
+ onClick: () => onDelete(entry),
209
+ marginLeft: 1,
210
+ children: /* @__PURE__ */ jsx(Trash, {})
211
+ }
212
+ ) : null
213
+ ] }) })
214
+ ]
215
+ }
216
+ );
217
+ };
218
+ function InputContentSuggestions({
219
+ uniqueId,
220
+ suggestions,
221
+ selected,
222
+ onAddEntry,
223
+ onDeleteEntry,
224
+ onEntriesSorted,
225
+ maximum
226
+ }) {
227
+ const { translate } = useTranslate();
228
+ const suggestionAsSelectedEntry = useMemo(() => {
229
+ return (suggestions || []).flatMap(
230
+ (suggestion) => suggestion.results.map((entrySuggestion) => ({
231
+ displayName: suggestion.displayName,
232
+ item: entrySuggestion,
233
+ searchableField: suggestion.searchableField,
234
+ uid: suggestion.uid
235
+ }))
236
+ ).slice(0, 10);
237
+ }, [suggestions]);
238
+ const buildSelectedId = (entry) => {
239
+ return `${uniqueId}-${entry.uid}-${entry.item.documentId}`;
240
+ };
241
+ const availableSuggestions = useMemo(() => {
242
+ const selectedIdentifiers = (selected || []).map(buildSelectedId);
243
+ return suggestionAsSelectedEntry.filter(
244
+ (suggestion) => !selectedIdentifiers.includes(buildSelectedId(suggestion))
245
+ );
246
+ }, [suggestions, selected]);
247
+ const onAdd = (entry) => {
248
+ if (typeof onAddEntry === "function") {
249
+ onAddEntry(entry);
250
+ }
251
+ };
252
+ const onDelete = (entry) => {
253
+ if (typeof onDeleteEntry === "function") {
254
+ onDeleteEntry(entry);
255
+ }
256
+ };
257
+ const onSort = (entries) => {
258
+ if (typeof onEntriesSorted === "function") {
259
+ onEntriesSorted(entries);
260
+ }
261
+ };
262
+ const sensors = useSensors(
263
+ useSensor(PointerSensor)
264
+ );
265
+ const sortableItems = useMemo(() => {
266
+ const items = selected?.map((entry) => buildSelectedId(entry)) ?? [];
267
+ return items;
268
+ }, [selected]);
269
+ const handleDragEnd = (event) => {
270
+ const { active, over } = event;
271
+ if (!active || !over) return;
272
+ if (active.id !== over.id) {
273
+ const oldIndex = selected.findIndex(
274
+ (entry) => buildSelectedId(entry) === active.id
275
+ );
276
+ const newIndex = selected.findIndex(
277
+ (entry) => buildSelectedId(entry) === over.id
278
+ );
279
+ onSort(arrayMove(selected, oldIndex, newIndex));
280
+ }
281
+ };
282
+ if (!availableSuggestions?.length && !selected?.length) return null;
283
+ return /* @__PURE__ */ jsx(Box, { padding: [2, 0, 2, 0], background: "neutral100", children: /* @__PURE__ */ jsxs(Table, { style: { whiteSpace: "unset", borderCollapse: "separate", borderSpacing: "0 10px" }, children: [
284
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
285
+ /* @__PURE__ */ jsx(Th, {}),
286
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: translate("contentSuggestions.title") }) }),
287
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: translate("contentSuggestions.contentType") }) }),
288
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: translate("contentSuggestions.state") }) })
289
+ ] }) }),
290
+ /* @__PURE__ */ jsxs(Tbody, { children: [
291
+ selected?.length ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
292
+ DndContext,
293
+ {
294
+ sensors,
295
+ collisionDetection: closestCenter,
296
+ onDragEnd: handleDragEnd,
297
+ children: /* @__PURE__ */ jsx(
298
+ SortableContext,
299
+ {
300
+ items: sortableItems,
301
+ strategy: verticalListSortingStrategy,
302
+ children: selected.map((entry) => /* @__PURE__ */ jsx(
303
+ TableItem,
304
+ {
305
+ id: buildSelectedId(entry),
306
+ entry,
307
+ type: "selected",
308
+ onDelete
309
+ },
310
+ buildSelectedId(entry)
311
+ ))
312
+ }
313
+ )
314
+ }
315
+ ) }) : null,
316
+ availableSuggestions.length && selected?.length ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx("hr", { style: { width: "100%" } }) }) }) : null,
317
+ availableSuggestions.map((entry) => /* @__PURE__ */ jsx(
318
+ TableItem,
319
+ {
320
+ id: buildSelectedId(entry),
321
+ entry,
322
+ type: "suggestion",
323
+ onAdd,
324
+ disabled: typeof maximum === "number" ? (selected?.length ?? 0) >= maximum : false
325
+ },
326
+ buildSelectedId(entry)
327
+ ))
328
+ ] })
329
+ ] }) });
330
+ }
331
+ const MainInput = ({
332
+ name,
333
+ error,
334
+ description,
335
+ onChange,
336
+ value,
337
+ labelAction,
338
+ label,
339
+ attribute,
340
+ required
341
+ }) => {
342
+ const { formatMessage } = useIntl();
343
+ const { translate } = useTranslate();
344
+ const location = useLocation();
345
+ const maximumItems = attribute.options.max;
346
+ const minimumItems = attribute.options.min || 0;
347
+ const currentLocale = new URLSearchParams(location.search).get(
348
+ "plugins[i18n][locale]"
349
+ );
350
+ const [keyword, setKeyword] = useState("");
351
+ const [selected, setSelected] = useState([]);
352
+ const [loading, setLoading] = useState(true);
353
+ useEffect(() => {
354
+ const value2 = selected.length > maximumItems || selected.length < minimumItems ? [] : selected;
355
+ onChange({ target: { name, value: formatToStrapiField(value2, currentLocale) } });
356
+ }, [selected]);
357
+ const hint = useMemo(() => {
358
+ const minLabel = minimumItems > 0 ? `${translate("input.hint.min")} ${minimumItems} ${minimumItems > 1 ? translate("input.hint.entries") : translate("input.hint.entry")}` : "";
359
+ const maxLabel = maximumItems > 0 ? `${translate("input.hint.max")} ${maximumItems} ${maximumItems > 1 ? translate("input.hint.entries") : translate("input.hint.entry")}` : "";
360
+ return `
361
+ ${minLabel ? `${minLabel}` : ""}
362
+ ${minLabel && maxLabel ? ", " : ""}
363
+ ${maxLabel}
364
+ ${minLabel || maxLabel ? translate("input.hint.separator") : ""}
365
+ ${selected.length} ${translate("input.hint.selected")}
366
+ `;
367
+ }, [selected, maximumItems, minimumItems, translate]);
368
+ const inputError = useMemo(() => {
369
+ if (!error) return "";
370
+ if (selected.length < minimumItems)
371
+ return `${error} - ${translate("input.error.min")} ${minimumItems} ${translate("input.error.required")}`;
372
+ if (selected.length > maximumItems)
373
+ return `${error} - ${translate("input.error.max")} ${maximumItems} ${translate("input.error.required")}`;
374
+ return error;
375
+ }, [error, maximumItems, minimumItems, selected, translate]);
376
+ const { loading: searchLoading, results } = useSearchedEntries(
377
+ keyword,
378
+ attribute.options.contentTypes,
379
+ currentLocale
380
+ );
381
+ useEffect(() => {
382
+ async function validateContent() {
383
+ if (!value) {
384
+ setLoading(false);
385
+ return;
386
+ }
387
+ const entries = JSON.parse(value);
388
+ const result = await validateCurrentRelations(entries);
389
+ setSelected(result);
390
+ setLoading(false);
391
+ }
392
+ validateContent();
393
+ }, []);
394
+ const onAddEntry = (entry) => {
395
+ const alreadyDefined = selected.some(
396
+ (selectedEntry) => selectedEntry.uid === entry.uid && selectedEntry.item.id === entry.item.id
397
+ );
398
+ if (alreadyDefined) return;
399
+ setSelected([...selected, entry]);
400
+ };
401
+ const onDeleteEntry = (entry) => {
402
+ const newSelected = selected.filter(
403
+ (selectedEntry) => !(selectedEntry.uid === entry.uid && selectedEntry.item.id === entry.item.id)
404
+ );
405
+ setSelected(newSelected);
406
+ };
407
+ const onEntriesSorted = (entries) => {
408
+ setSelected(entries);
409
+ };
410
+ if (loading) return /* @__PURE__ */ jsx(Loader, {});
411
+ return /* @__PURE__ */ jsxs(
412
+ Field.Root,
413
+ {
414
+ name,
415
+ id: name,
416
+ error,
417
+ hint: description,
418
+ required,
419
+ children: [
420
+ /* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: label }),
421
+ /* @__PURE__ */ jsx(
422
+ TextInput,
423
+ {
424
+ placeholder: translate("input.placeholder"),
425
+ required,
426
+ hint,
427
+ error: inputError,
428
+ value: keyword,
429
+ onChange: (e) => setKeyword(e.target.value)
430
+ }
431
+ ),
432
+ searchLoading ? /* @__PURE__ */ jsx(Loader, {}) : /* @__PURE__ */ jsx(
433
+ InputContentSuggestions,
434
+ {
435
+ uniqueId: Date.now(),
436
+ suggestions: results,
437
+ selected,
438
+ onAddEntry,
439
+ onDeleteEntry,
440
+ onEntriesSorted,
441
+ maximum: maximumItems
442
+ }
443
+ )
444
+ ]
445
+ }
446
+ );
447
+ };
448
+ const Index = (props) => {
449
+ const { locale } = useIntl();
450
+ const theme = useMemo(() => ({}), []);
451
+ const attribute = useMemo(() => {
452
+ if (!props.attribute) return props.attribute;
453
+ if (!props.attribute.options) return props.attribute;
454
+ if (!props.attribute.options.contentTypes) return props.attribute;
455
+ const contentTypes = Object.keys(
456
+ props.attribute.options.contentTypes
457
+ ).filter((key) => props.attribute.options.contentTypes[key]);
458
+ return {
459
+ ...props.attribute,
460
+ options: {
461
+ ...props.attribute.options,
462
+ contentTypes: contentTypes.join(",")
463
+ }
464
+ };
465
+ }, [props.attribute]);
466
+ return /* @__PURE__ */ jsx(DesignSystemProvider, { theme, children: /* @__PURE__ */ jsx(MainInput, { ...props, attribute }) });
467
+ };
468
+ export {
469
+ Index as default
470
+ };
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-BtldAbIW.js");
2
+ const index = require("../_chunks/index-CEXSrD4g.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-DdX2rVzB.mjs";
1
+ import { i } from "../_chunks/index-BGSdnZnr.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -6,8 +6,7 @@ type Props = {
6
6
  onAddEntry?(entry: SelectedEntry): void;
7
7
  onDeleteEntry?(entry: SelectedEntry): void;
8
8
  onEntriesSorted?(entries: SelectedEntry[]): void;
9
- sortable?: boolean;
10
9
  maximum?: number;
11
10
  };
12
- export declare function InputContentSuggestions({ uniqueId, suggestions, selected, onAddEntry, onDeleteEntry, onEntriesSorted, maximum, sortable }: Props): import("react/jsx-runtime").JSX.Element | null;
11
+ export declare function InputContentSuggestions({ uniqueId, suggestions, selected, onAddEntry, onDeleteEntry, onEntriesSorted, maximum, }: Props): import("react/jsx-runtime").JSX.Element | null;
13
12
  export {};
@@ -1,12 +1,11 @@
1
1
  import { SelectedEntry } from '../../interface';
2
2
  type Props = {
3
3
  entry: SelectedEntry;
4
+ id: string;
4
5
  type: 'suggestion' | 'selected';
5
- uniqueId: number;
6
6
  onAdd?(entry: SelectedEntry): void;
7
7
  onDelete?(entry: SelectedEntry): void;
8
8
  disabled?: boolean;
9
- sortable?: boolean;
10
9
  };
11
- export declare const TableItem: ({ entry, type, uniqueId, disabled, onAdd, onDelete }: Props) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const TableItem: ({ entry, id, type, disabled, onAdd, onDelete }: Props) => import("react/jsx-runtime").JSX.Element;
12
11
  export {};
@@ -1,5 +1,5 @@
1
1
  import { FormattedStrapiEntry, MatchingContentResponse, SelectedEntry } from '../interface';
2
2
  export declare const fetchMatchingContent: (keyword: string, contentTypes: string, locale: string) => Promise<MatchingContentResponse>;
3
- export declare const formatToStrapiField: (entries: SelectedEntry[]) => string;
3
+ export declare const formatToStrapiField: (entries: SelectedEntry[], locale: string) => string;
4
4
  export declare const validateCurrentRelations: (entries: FormattedStrapiEntry[]) => Promise<SelectedEntry[]>;
5
5
  export declare const listContentTypes: () => Promise<any>;