strapi-plugin-timeline 0.0.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.
@@ -0,0 +1,259 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Main, Typography, Box, Flex, Button, Field, SingleSelect, SingleSelectOption, Accordion, Switch, IconButton, Grid, MultiSelect, MultiSelectOption, NumberInput, Dialog } from "@strapi/design-system";
4
+ import { Check, Plus, Trash } from "@strapi/icons";
5
+ import { useFetchClient, useNotification, Layouts } from "@strapi/strapi/admin";
6
+ import { P as PLUGIN_ID } from "./index-DWXpMwlf.mjs";
7
+ const ALL_ACTIONS = ["create", "update", "delete", "publish"];
8
+ const DURATION_UNITS = [
9
+ { value: "hours", label: "Hours" },
10
+ { value: "days", label: "Days" },
11
+ { value: "weeks", label: "Weeks" },
12
+ { value: "months", label: "Months" }
13
+ ];
14
+ const makeDefaultConfig = () => ({
15
+ enabled: true,
16
+ actions: [...ALL_ACTIONS],
17
+ entriesLimit: 0,
18
+ durationLimit: 0,
19
+ durationUnit: "days"
20
+ });
21
+ const SettingsPage = () => {
22
+ const { get, put } = useFetchClient();
23
+ const { toggleNotification } = useNotification();
24
+ const [contentTypes, setContentTypes] = useState([]);
25
+ const [configs, setConfigs] = useState({});
26
+ const [loading, setLoading] = useState(true);
27
+ const [saving, setSaving] = useState(false);
28
+ const [addingUid, setAddingUid] = useState("");
29
+ const [expandedItem, setExpandedItem] = useState("");
30
+ const [removeTarget, setRemoveTarget] = useState(null);
31
+ useEffect(() => {
32
+ loadData();
33
+ }, []);
34
+ const loadData = async () => {
35
+ try {
36
+ const [settingsRes, ctRes] = await Promise.all([
37
+ get(`/${PLUGIN_ID}/settings`),
38
+ get(`/${PLUGIN_ID}/content-types`)
39
+ ]);
40
+ const raw = settingsRes.data.data || {};
41
+ setConfigs(raw.contentTypeConfigs || {});
42
+ setContentTypes(ctRes.data.data || []);
43
+ } catch {
44
+ toggleNotification({ type: "danger", message: "Failed to load settings." });
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ };
49
+ const handleSave = async () => {
50
+ setSaving(true);
51
+ try {
52
+ await put(`/${PLUGIN_ID}/settings`, { contentTypeConfigs: configs });
53
+ toggleNotification({ type: "success", message: "Settings saved successfully." });
54
+ } catch {
55
+ toggleNotification({ type: "danger", message: "Failed to save settings." });
56
+ } finally {
57
+ setSaving(false);
58
+ }
59
+ };
60
+ const availableContentTypes = contentTypes.filter((ct) => !configs[ct.uid]);
61
+ const addContentType = () => {
62
+ if (!addingUid) return;
63
+ setConfigs((prev) => ({ ...prev, [addingUid]: makeDefaultConfig() }));
64
+ setExpandedItem(addingUid);
65
+ setAddingUid("");
66
+ };
67
+ const removeContentType = (uid) => {
68
+ setConfigs((prev) => {
69
+ const next = { ...prev };
70
+ delete next[uid];
71
+ return next;
72
+ });
73
+ setExpandedItem((prev) => prev === uid ? "" : prev);
74
+ };
75
+ const updateConfig = (uid, field, value) => {
76
+ setConfigs((prev) => ({
77
+ ...prev,
78
+ [uid]: { ...prev[uid], [field]: value }
79
+ }));
80
+ };
81
+ const getDisplayName = (uid) => {
82
+ const ct = contentTypes.find((c) => c.uid === uid);
83
+ return ct?.displayName || uid;
84
+ };
85
+ const getKindLabel = (uid) => {
86
+ const ct = contentTypes.find((c) => c.uid === uid);
87
+ return ct?.kind === "singleType" ? "Single Type" : "Collection Type";
88
+ };
89
+ if (loading) {
90
+ return /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(Main, { padding: 8, children: /* @__PURE__ */ jsx(Typography, { variant: "alpha", children: "Loading…" }) }) });
91
+ }
92
+ const configuredUids = Object.keys(configs);
93
+ return /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsxs(Main, { padding: 8, children: [
94
+ /* @__PURE__ */ jsx(Box, { paddingBottom: 6, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
95
+ /* @__PURE__ */ jsxs(Box, { children: [
96
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "h1", children: "Timeline Settings" }),
97
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: "Configure which content types to track and how to manage their history." })
98
+ ] }),
99
+ /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Check, {}), onClick: handleSave, loading: saving, disabled: saving, children: "Save" })
100
+ ] }) }),
101
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", shadow: "tableShadow", padding: 6, borderRadius: "4px", children: [
102
+ /* @__PURE__ */ jsxs(Box, { paddingBottom: 4, children: [
103
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: "Tracked Content Types" }),
104
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "Add content types to track and configure actions, entry limits, and retention duration for each." })
105
+ ] }),
106
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, paddingBottom: 4, alignItems: "flex-end", children: [
107
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
108
+ /* @__PURE__ */ jsx(Field.Label, { children: "Add Content Type" }),
109
+ /* @__PURE__ */ jsx(
110
+ SingleSelect,
111
+ {
112
+ value: addingUid,
113
+ onChange: (value) => setAddingUid(String(value)),
114
+ placeholder: "Select a content type to add...",
115
+ disabled: availableContentTypes.length === 0,
116
+ children: availableContentTypes.map((ct) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: ct.uid, children: [
117
+ ct.displayName,
118
+ " (",
119
+ ct.kind === "singleType" ? "Single" : "Collection",
120
+ ")"
121
+ ] }, ct.uid))
122
+ }
123
+ )
124
+ ] }) }),
125
+ /* @__PURE__ */ jsx(
126
+ Button,
127
+ {
128
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
129
+ size: "L",
130
+ onClick: addContentType,
131
+ variant: "secondary",
132
+ disabled: !addingUid,
133
+ children: "Add"
134
+ }
135
+ )
136
+ ] }),
137
+ configuredUids.length === 0 ? /* @__PURE__ */ jsx(Box, { padding: 4, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No content types configured. Select one above to start tracking changes." }) }) : /* @__PURE__ */ jsx(
138
+ Accordion.Root,
139
+ {
140
+ value: expandedItem,
141
+ onValueChange: (value) => setExpandedItem(value),
142
+ children: configuredUids.map((uid) => {
143
+ const config = configs[uid];
144
+ return /* @__PURE__ */ jsxs(Accordion.Item, { value: uid, children: [
145
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
146
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: getDisplayName(uid) }),
147
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: getKindLabel(uid) }),
148
+ !config.enabled && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: "(disabled)" })
149
+ ] }) }) }),
150
+ /* @__PURE__ */ jsx(Accordion.Content, { children: /* @__PURE__ */ jsxs(Box, { padding: 4, children: [
151
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
152
+ /* @__PURE__ */ jsx(
153
+ Switch,
154
+ {
155
+ onCheckedChange: (checked) => updateConfig(uid, "enabled", checked),
156
+ checked: config.enabled,
157
+ visibleLabels: true
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsx(
161
+ IconButton,
162
+ {
163
+ onClick: () => setRemoveTarget(uid),
164
+ label: "Remove content type",
165
+ variant: "danger-light",
166
+ children: /* @__PURE__ */ jsx(Trash, {})
167
+ }
168
+ )
169
+ ] }),
170
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, gridCols: 2, children: [
171
+ /* @__PURE__ */ jsx(Grid.Item, { col: 2, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", hint: "Which lifecycle actions to record. Empty = all actions.", children: [
172
+ /* @__PURE__ */ jsx(Field.Label, { children: "Tracked Actions" }),
173
+ /* @__PURE__ */ jsx(
174
+ MultiSelect,
175
+ {
176
+ value: config.actions,
177
+ onChange: (values) => updateConfig(uid, "actions", values),
178
+ placeholder: "Select actions to track...",
179
+ withTags: true,
180
+ children: ALL_ACTIONS.map((action) => /* @__PURE__ */ jsx(MultiSelectOption, { value: action, children: action.charAt(0).toUpperCase() + action.slice(1) }, action))
181
+ }
182
+ ),
183
+ /* @__PURE__ */ jsx(Field.Hint, {})
184
+ ] }) }),
185
+ /* @__PURE__ */ jsx(Grid.Item, { col: 1, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", hint: "Max entries to keep per content type. 0 = unlimited.", children: [
186
+ /* @__PURE__ */ jsx(Field.Label, { children: "Entries Limit" }),
187
+ /* @__PURE__ */ jsx(
188
+ NumberInput,
189
+ {
190
+ name: `${uid}-entriesLimit`,
191
+ value: config.entriesLimit,
192
+ onValueChange: (value) => updateConfig(uid, "entriesLimit", value ?? 0),
193
+ step: 10
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsx(Field.Hint, {})
197
+ ] }) }),
198
+ /* @__PURE__ */ jsx(Grid.Item, { col: 1, s: 2, alignItems: "flex-start", children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", hint: "Entries older than this are cleaned up by the scheduled task. 0 = no limit.", children: [
199
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-end", children: [
200
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
201
+ /* @__PURE__ */ jsx(Field.Label, { children: "Retention Duration" }),
202
+ /* @__PURE__ */ jsx(
203
+ NumberInput,
204
+ {
205
+ name: `${uid}-durationLimit`,
206
+ value: config.durationLimit,
207
+ onValueChange: (value) => updateConfig(uid, "durationLimit", value ?? 0),
208
+ step: 1
209
+ }
210
+ )
211
+ ] }) }),
212
+ /* @__PURE__ */ jsx(Box, { style: { minWidth: "120px" }, children: /* @__PURE__ */ jsx(
213
+ SingleSelect,
214
+ {
215
+ value: config.durationUnit,
216
+ onChange: (value) => updateConfig(uid, "durationUnit", String(value)),
217
+ children: DURATION_UNITS.map((u) => /* @__PURE__ */ jsx(SingleSelectOption, { value: u.value, children: u.label }, u.value))
218
+ }
219
+ ) })
220
+ ] }),
221
+ /* @__PURE__ */ jsx(Field.Hint, {})
222
+ ] }) })
223
+ ] })
224
+ ] }) })
225
+ ] }, uid);
226
+ })
227
+ }
228
+ )
229
+ ] }),
230
+ /* @__PURE__ */ jsx(Dialog.Root, { open: !!removeTarget, onOpenChange: (open) => !open && setRemoveTarget(null), children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
231
+ /* @__PURE__ */ jsx(Dialog.Header, { children: "Remove content type from tracking" }),
232
+ /* @__PURE__ */ jsx(Dialog.Body, { icon: /* @__PURE__ */ jsx(Trash, {}), children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, children: [
233
+ /* @__PURE__ */ jsxs(Typography, { children: [
234
+ "Are you sure you want to stop tracking ",
235
+ /* @__PURE__ */ jsx("strong", { children: removeTarget ? getDisplayName(removeTarget) : "" }),
236
+ "?"
237
+ ] }),
238
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Existing timeline entries for this content type will not be deleted. You can re-add it later to resume tracking." })
239
+ ] }) }),
240
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
241
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: "Cancel" }) }),
242
+ /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(
243
+ Button,
244
+ {
245
+ variant: "danger",
246
+ onClick: () => {
247
+ if (removeTarget) removeContentType(removeTarget);
248
+ setRemoveTarget(null);
249
+ },
250
+ children: "Remove"
251
+ }
252
+ ) })
253
+ ] })
254
+ ] }) })
255
+ ] }) });
256
+ };
257
+ export {
258
+ SettingsPage
259
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const en = {};
4
+ exports.default = en;
@@ -0,0 +1,4 @@
1
+ const en = {};
2
+ export {
3
+ en as default
4
+ };