strapi-plugin-document-metadata 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/LICENSE +19 -0
- package/README.md +67 -0
- package/dist/_chunks/en-h9IAaoUJ.mjs +16 -0
- package/dist/_chunks/en-tDvQkVOI.js +16 -0
- package/dist/admin/index.js +311 -0
- package/dist/admin/index.mjs +312 -0
- package/dist/admin/src/components/DocumentMetadataCard/index.d.ts +10 -0
- package/dist/admin/src/components/DocumentMetadataGuard/index.d.ts +6 -0
- package/dist/admin/src/components/Initializer/index.d.ts +5 -0
- package/dist/admin/src/components/LastOpenedMetadataGuard/index.d.ts +10 -0
- package/dist/admin/src/components/LastOpenedMetadataLoader/index.d.ts +10 -0
- package/dist/admin/src/components/LastOpenedMetadataLoader/useLastOpened.d.ts +31 -0
- package/dist/admin/src/components/MetadataRow/index.d.ts +9 -0
- package/dist/admin/src/index.d.ts +10 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/prefixKey.d.ts +2 -0
- package/dist/admin/src/utils/recentTimeFormatter.d.ts +16 -0
- package/dist/admin/src/utils/relativeDateFormatter.d.ts +30 -0
- package/dist/server/index.js +119 -0
- package/dist/server/index.mjs +120 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +5 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/controller.d.ts +12 -0
- package/dist/server/src/controllers/index.d.ts +9 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +58 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +14 -0
- package/dist/server/src/services/index.d.ts +19 -0
- package/dist/server/src/services/service.d.ts +35 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Felix Mau <me@felix.hamburg>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Strapi Plugin: Document Metadata
|
|
2
|
+
|
|
3
|
+
A Strapi plugin that displays entity metadata, with an option to include the **"last opened"** details.
|
|
4
|
+
|
|
5
|
+
## ⏳ Installation
|
|
6
|
+
|
|
7
|
+
Install with NPM.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install strapi-plugin-document-metadata --save
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Install with Yarn.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add strapi-plugin-document-metadata
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 🔧 Usage
|
|
20
|
+
|
|
21
|
+
### 1. Configure the Plugin
|
|
22
|
+
|
|
23
|
+
Add the following configuration to your `config/plugins.ts` file. Create the file if it doesn’t already exist:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
export default {
|
|
27
|
+
// …
|
|
28
|
+
'document-metadata': {
|
|
29
|
+
enabled: true,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then restart the app to apply the changes.
|
|
35
|
+
|
|
36
|
+
### 2. Use in the Admin Panel
|
|
37
|
+
|
|
38
|
+
Once the plugin is installed, the **Document Metadata** card automatically appears when editing **collection types entries**. By default, it displays:
|
|
39
|
+
|
|
40
|
+
- Created at
|
|
41
|
+
- Created by
|
|
42
|
+
- Last updated at
|
|
43
|
+
- Last updated by
|
|
44
|
+
|
|
45
|
+
No additional setup is required for these fields.
|
|
46
|
+
|
|
47
|
+
#### Last Opened Metadata
|
|
48
|
+
|
|
49
|
+
To enable **Last Opened** tracking, add the following fields to your collection type using the Content-Type Builder:
|
|
50
|
+
|
|
51
|
+
| Field name | Type | Required |
|
|
52
|
+
| ---------- | -------- | -------- |
|
|
53
|
+
| `openedAt` | DateTime | ❌ |
|
|
54
|
+
| `openedBy` | String | ❌ |
|
|
55
|
+
|
|
56
|
+
Once these fields exist:
|
|
57
|
+
|
|
58
|
+
- The plugin automatically updates them when a document is opened.
|
|
59
|
+
- The metadata card will display the last opened date and user.
|
|
60
|
+
|
|
61
|
+
**Note:** To prevent these fields from showing up in the regular edit view, remove them under "Configure the view".
|
|
62
|
+
|
|
63
|
+
## 📸 Screenshots
|
|
64
|
+
|
|
65
|
+
Below are screenshots from an example application showing the document metadata in the list view and when editing an entry.
|
|
66
|
+
|
|
67
|
+
<a href="./assets/content-type-builder.png"/><img src="./assets/content-type-builder-thumb.png" alt="Add openedAt and openedBy in the content type builder." /></a> <a href="./assets/content-manager-list-view.png"/><img src="./assets/content-manager-list-view-thumb.png" alt="Opened at and opened by in the list view." /></a> <a href="./assets/content-manager-first-time-opened.png"/><img src="./assets/content-manager-first-time-opened-thumb.png" alt="Document Metadata when opened for the first time." /></a> <a href="./assets/content-manager-last-time-opened.png"/><img src="./assets/content-manager-last-time-opened-thumb.png" alt="Document Metadata." /></a>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const en = {
|
|
2
|
+
"document-metadata.title": "Metadata",
|
|
3
|
+
"document-metadata.opened-at": "Opened at",
|
|
4
|
+
"document-metadata.opened-by": "by {username}",
|
|
5
|
+
"document-metadata.opened-first-time": "Opened for the first time.",
|
|
6
|
+
"document-metadata.updated-at": "Updated at",
|
|
7
|
+
"document-metadata.updated-by": "by {username}",
|
|
8
|
+
"document-metadata.created-at": "Created at",
|
|
9
|
+
"document-metadata.created-by": "by {username}",
|
|
10
|
+
"document-metadata.date.today": "Today, {formattedTime}",
|
|
11
|
+
"document-metadata.date.yesterday": "Yesterday, {formattedTime}",
|
|
12
|
+
"document-metadata.date.other": "{formattedDate}"
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
en as default
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const en = {
|
|
4
|
+
"document-metadata.title": "Metadata",
|
|
5
|
+
"document-metadata.opened-at": "Opened at",
|
|
6
|
+
"document-metadata.opened-by": "by {username}",
|
|
7
|
+
"document-metadata.opened-first-time": "Opened for the first time.",
|
|
8
|
+
"document-metadata.updated-at": "Updated at",
|
|
9
|
+
"document-metadata.updated-by": "by {username}",
|
|
10
|
+
"document-metadata.created-at": "Created at",
|
|
11
|
+
"document-metadata.created-by": "by {username}",
|
|
12
|
+
"document-metadata.date.today": "Today, {formattedTime}",
|
|
13
|
+
"document-metadata.date.yesterday": "Yesterday, {formattedTime}",
|
|
14
|
+
"document-metadata.date.other": "{formattedDate}"
|
|
15
|
+
};
|
|
16
|
+
exports.default = en;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const reactRouterDom = require("react-router-dom");
|
|
5
|
+
const admin = require("@strapi/strapi/admin");
|
|
6
|
+
const designSystem = require("@strapi/design-system");
|
|
7
|
+
const icons = require("@strapi/icons");
|
|
8
|
+
const reactIntl = require("react-intl");
|
|
9
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
10
|
+
const v = glob[path];
|
|
11
|
+
if (v) {
|
|
12
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
13
|
+
}
|
|
14
|
+
return new Promise((_, reject) => {
|
|
15
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
16
|
+
reject.bind(
|
|
17
|
+
null,
|
|
18
|
+
new Error(
|
|
19
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const PLUGIN_ID = "document-metadata";
|
|
26
|
+
const Initializer = ({ setPlugin }) => {
|
|
27
|
+
const ref = react.useRef(setPlugin);
|
|
28
|
+
react.useEffect(() => {
|
|
29
|
+
ref.current(PLUGIN_ID);
|
|
30
|
+
}, []);
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
const prefixKey = (key) => `${PLUGIN_ID}.${key}`;
|
|
34
|
+
const relativeDateFormatter = (date, textBuilder) => {
|
|
35
|
+
const today = /* @__PURE__ */ new Date();
|
|
36
|
+
if (today.toDateString() === date.toDateString()) {
|
|
37
|
+
return textBuilder.today(date.toLocaleTimeString());
|
|
38
|
+
}
|
|
39
|
+
const yesterday = /* @__PURE__ */ new Date();
|
|
40
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
41
|
+
if (yesterday.toDateString() === date.toDateString()) {
|
|
42
|
+
return textBuilder.yesterday(date.toLocaleTimeString());
|
|
43
|
+
}
|
|
44
|
+
return textBuilder.other(date.toLocaleString());
|
|
45
|
+
};
|
|
46
|
+
const config$3 = {
|
|
47
|
+
/** The threshold in minutes to determine if a date is considered "recent". */
|
|
48
|
+
thresholdInMinutes: 60
|
|
49
|
+
};
|
|
50
|
+
const recentTimeFormatter = ({
|
|
51
|
+
date,
|
|
52
|
+
fallbackFormatter
|
|
53
|
+
}) => {
|
|
54
|
+
const now = /* @__PURE__ */ new Date();
|
|
55
|
+
const minutesElapsedSinceNow = minutesElapsed(now, date);
|
|
56
|
+
if (minutesElapsedSinceNow >= config$3.thresholdInMinutes) {
|
|
57
|
+
return fallbackFormatter(date);
|
|
58
|
+
}
|
|
59
|
+
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
60
|
+
return rtf.format(-minutesElapsedSinceNow, "minutes");
|
|
61
|
+
};
|
|
62
|
+
const minutesElapsed = (now, then) => {
|
|
63
|
+
const millisecondsPerMinute = 60 * 1e3;
|
|
64
|
+
return Math.floor((now.getTime() - then.getTime()) / millisecondsPerMinute);
|
|
65
|
+
};
|
|
66
|
+
const MetadataRow = ({ title, line1, line2 }) => {
|
|
67
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Item, { direction: "column", alignItems: "stretch", children: [
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: title }),
|
|
69
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
70
|
+
line1,
|
|
71
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
72
|
+
line2
|
|
73
|
+
] })
|
|
74
|
+
] });
|
|
75
|
+
};
|
|
76
|
+
var FetchStatus = /* @__PURE__ */ ((FetchStatus2) => {
|
|
77
|
+
FetchStatus2["Initial"] = "initial";
|
|
78
|
+
FetchStatus2["InProgress"] = "in-progress";
|
|
79
|
+
FetchStatus2["Success"] = "success";
|
|
80
|
+
FetchStatus2["Failure"] = "failure";
|
|
81
|
+
return FetchStatus2;
|
|
82
|
+
})(FetchStatus || {});
|
|
83
|
+
const config$2 = {
|
|
84
|
+
/** The configuration for the last-opened request. */
|
|
85
|
+
lastOpenedRequest: {
|
|
86
|
+
/** The path to fetch the last-opened metadata for the entity with the given `uid` and `documentId`. */
|
|
87
|
+
path: (uid, documentId) => `/document-metadata/last-opened/${uid}/${documentId}`
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const useLastOpened = ({
|
|
91
|
+
uid,
|
|
92
|
+
documentId,
|
|
93
|
+
locale
|
|
94
|
+
}) => {
|
|
95
|
+
const fetchClient = admin.useFetchClient();
|
|
96
|
+
const [lastOpenedFetchState, setLastOpenedFetchState] = react.useState({
|
|
97
|
+
status: "initial"
|
|
98
|
+
/* Initial */
|
|
99
|
+
});
|
|
100
|
+
react.useEffect(() => {
|
|
101
|
+
const fetchLastOpened = async () => {
|
|
102
|
+
setLastOpenedFetchState({
|
|
103
|
+
status: "in-progress"
|
|
104
|
+
/* InProgress */
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
const { data: lastOpened } = await fetchClient.get(
|
|
108
|
+
config$2.lastOpenedRequest.path(uid, documentId),
|
|
109
|
+
{
|
|
110
|
+
params: { locale }
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
setLastOpenedFetchState({ status: "success", lastOpened });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`Failed to fetch last-opened metadata: ${error}`);
|
|
116
|
+
setLastOpenedFetchState({ status: "failure", error });
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
fetchLastOpened();
|
|
120
|
+
}, [uid, documentId, locale]);
|
|
121
|
+
return lastOpenedFetchState;
|
|
122
|
+
};
|
|
123
|
+
const config$1 = {
|
|
124
|
+
/** The loading text to show while fetching data. */
|
|
125
|
+
loadingString: "…",
|
|
126
|
+
/** The non-breaking space character. */
|
|
127
|
+
nonBreakingSpace: " "
|
|
128
|
+
};
|
|
129
|
+
const LastOpenedMetadataLoader = ({
|
|
130
|
+
uid,
|
|
131
|
+
documentId,
|
|
132
|
+
locale
|
|
133
|
+
}) => {
|
|
134
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
135
|
+
const translate = (key, values) => formatMessage({ id: prefixKey(key) }, values);
|
|
136
|
+
const lastOpenedFetchState = useLastOpened({ uid, documentId, locale });
|
|
137
|
+
switch (lastOpenedFetchState.status) {
|
|
138
|
+
case FetchStatus.Initial:
|
|
139
|
+
case FetchStatus.InProgress:
|
|
140
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
141
|
+
MetadataRow,
|
|
142
|
+
{
|
|
143
|
+
title: translate("opened-at"),
|
|
144
|
+
line1: config$1.loadingString,
|
|
145
|
+
line2: config$1.nonBreakingSpace
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
case FetchStatus.Failure:
|
|
149
|
+
return null;
|
|
150
|
+
case FetchStatus.Success:
|
|
151
|
+
const lastOpened = lastOpenedFetchState.lastOpened;
|
|
152
|
+
if (!lastOpened.openedAt || !lastOpened.openedBy) {
|
|
153
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MetadataRow, { title: translate("opened-at"), line1: translate("opened-first-time") });
|
|
154
|
+
}
|
|
155
|
+
const formattedOpenedAt = recentTimeFormatter({
|
|
156
|
+
date: new Date(lastOpened.openedAt),
|
|
157
|
+
fallbackFormatter: (date) => relativeDateFormatter(date, {
|
|
158
|
+
today: (formattedTime) => translate("date.today", { formattedTime }),
|
|
159
|
+
yesterday: (formattedTime) => translate("date.yesterday", { formattedTime }),
|
|
160
|
+
other: (formattedDate) => translate("date.other", { formattedDate })
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
const formattedOpenedBy = translate("opened-by", { username: lastOpened.openedBy });
|
|
164
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
165
|
+
MetadataRow,
|
|
166
|
+
{
|
|
167
|
+
title: translate("opened-at"),
|
|
168
|
+
line1: formattedOpenedAt,
|
|
169
|
+
line2: formattedOpenedBy
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const LastOpenedMetadataGuard = ({
|
|
175
|
+
uid,
|
|
176
|
+
document
|
|
177
|
+
}) => {
|
|
178
|
+
const hasLastOpenedFields = "openedAt" in document && "openedBy" in document;
|
|
179
|
+
if (!hasLastOpenedFields) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LastOpenedMetadataLoader, { uid, documentId: document.documentId, locale: document.locale });
|
|
183
|
+
};
|
|
184
|
+
const DocumentMetadataCard = ({
|
|
185
|
+
collectionType,
|
|
186
|
+
uid,
|
|
187
|
+
documentId
|
|
188
|
+
}) => {
|
|
189
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
190
|
+
const translate = (key, values) => formatMessage({ id: prefixKey(key) }, values);
|
|
191
|
+
const initialParams = { plugins: { i18n: { locale: void 0 } } };
|
|
192
|
+
const [queryParams, _] = admin.useQueryParams(initialParams);
|
|
193
|
+
const locale = queryParams.query.plugins.i18n.locale;
|
|
194
|
+
const { document } = admin.unstable_useDocument({ documentId, model: uid, collectionType, params: { locale } });
|
|
195
|
+
if (!document) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const formatDate = (date) => recentTimeFormatter({
|
|
199
|
+
date: new Date(date),
|
|
200
|
+
fallbackFormatter: (date2) => relativeDateFormatter(date2, {
|
|
201
|
+
today: (formattedTime) => translate("date.today", { formattedTime }),
|
|
202
|
+
yesterday: (formattedTime) => translate("date.yesterday", { formattedTime }),
|
|
203
|
+
other: (formattedDate) => translate("date.other", { formattedDate })
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
const formatUsername = (user) => `${user.firstname} ${user.lastname}`;
|
|
207
|
+
let formattedUpdatedAt = formatDate(new Date(document.updatedAt));
|
|
208
|
+
let formattedUpdatedBy = document.updatedBy ? translate("updated-by", { username: formatUsername(document.updatedBy) }) : "";
|
|
209
|
+
let formattedCreatedAt = formatDate(new Date(document.createdAt));
|
|
210
|
+
let formattedCreatedBy = document.createdBy ? translate("created-by", { username: formatUsername(document.createdBy) }) : "";
|
|
211
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
212
|
+
designSystem.Box,
|
|
213
|
+
{
|
|
214
|
+
width: "100%",
|
|
215
|
+
padding: "16px",
|
|
216
|
+
background: "neutral0",
|
|
217
|
+
hasRadius: true,
|
|
218
|
+
borderColor: "neutral200",
|
|
219
|
+
borderStyle: "solid",
|
|
220
|
+
borderWidth: "1px",
|
|
221
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: "8px", gridCols: 1, children: [
|
|
222
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Item, { direction: "column", alignItems: "stretch", children: [
|
|
223
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: "8px", direction: "row", alignItems: "center", justifyContent: "space-between", children: [
|
|
224
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: /* @__PURE__ */ jsxRuntime.jsx(reactIntl.FormattedMessage, { id: prefixKey("title") }) }),
|
|
225
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Paperclip, { fill: "neutral600", style: { marginRight: "4px" } })
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { style: { marginTop: "6px", marginBottom: "4px" } })
|
|
228
|
+
] }),
|
|
229
|
+
/* @__PURE__ */ jsxRuntime.jsx(LastOpenedMetadataGuard, { uid, document }),
|
|
230
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
231
|
+
MetadataRow,
|
|
232
|
+
{
|
|
233
|
+
title: translate("updated-at"),
|
|
234
|
+
line1: formattedUpdatedAt,
|
|
235
|
+
line2: formattedUpdatedBy
|
|
236
|
+
}
|
|
237
|
+
),
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
239
|
+
MetadataRow,
|
|
240
|
+
{
|
|
241
|
+
title: translate("created-at"),
|
|
242
|
+
line1: formattedCreatedAt,
|
|
243
|
+
line2: formattedCreatedBy
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
] })
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
const config = {
|
|
251
|
+
/**
|
|
252
|
+
* The supported collection type.
|
|
253
|
+
* We currently only support "collection-types", as "single-types" don't have their document ID as part of the URL.
|
|
254
|
+
*/
|
|
255
|
+
supportedCollectionType: "collection-types",
|
|
256
|
+
/**
|
|
257
|
+
* The expected length of a valid document ID.
|
|
258
|
+
*
|
|
259
|
+
* > To address this limitation, Strapi 5 introduced documentId, a 24-character alphanumeric string, as a unique and
|
|
260
|
+
* > persistent identifier for a content entry, independent of its physical records.
|
|
261
|
+
*
|
|
262
|
+
* https://docs.strapi.io/cms/api/document-service
|
|
263
|
+
*/
|
|
264
|
+
documentIdLength: 24
|
|
265
|
+
};
|
|
266
|
+
const DocumentMetadataGuard = () => {
|
|
267
|
+
const location = reactRouterDom.useLocation();
|
|
268
|
+
const urlPathComponents = location.pathname.split("/").filter(Boolean);
|
|
269
|
+
const [collectionType, uid, documentId] = urlPathComponents.slice(-3);
|
|
270
|
+
const isValidCollectionType = collectionType === config.supportedCollectionType;
|
|
271
|
+
const isValidUID = uid && uid.length;
|
|
272
|
+
const isValidDocumentId = documentId && documentId.length === config.documentIdLength;
|
|
273
|
+
if (!isValidCollectionType || !isValidUID || !isValidDocumentId) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
277
|
+
DocumentMetadataCard,
|
|
278
|
+
{
|
|
279
|
+
collectionType,
|
|
280
|
+
uid,
|
|
281
|
+
documentId
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
const index = {
|
|
286
|
+
register(app) {
|
|
287
|
+
app.getPlugin("content-manager").injectComponent("editView", "right-links", {
|
|
288
|
+
name: "DocumentMetadataGuard",
|
|
289
|
+
Component: DocumentMetadataGuard
|
|
290
|
+
});
|
|
291
|
+
app.registerPlugin({
|
|
292
|
+
id: PLUGIN_ID,
|
|
293
|
+
initializer: Initializer,
|
|
294
|
+
isReady: false,
|
|
295
|
+
name: PLUGIN_ID
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
async registerTrads({ locales }) {
|
|
299
|
+
return Promise.all(
|
|
300
|
+
locales.map(async (locale) => {
|
|
301
|
+
try {
|
|
302
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-tDvQkVOI.js")) }), `./translations/${locale}.json`, 3);
|
|
303
|
+
return { data, locale };
|
|
304
|
+
} catch {
|
|
305
|
+
return { data: {}, locale };
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
module.exports = index;
|