@devx-labs/strapi-preview 1.0.3 → 1.0.4
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/dist/_chunks/PreviewImageInput-BcPJkyKS.mjs +22 -0
- package/dist/_chunks/PreviewImageInput-Bha74a3E.js +22 -0
- package/dist/admin/index.js +203 -0
- package/dist/admin/index.mjs +204 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.mjs +57 -0
- package/package.json +32 -15
- package/admin/src/components/ComponentPreviewPanel.tsx +0 -206
- package/admin/src/components/PreviewImageInput.tsx +0 -31
- package/admin/src/index.tsx +0 -47
- package/server/src/controllers/options.ts +0 -27
- package/server/src/index.ts +0 -43
- package/server/src/routes/index.ts +0 -10
- package/strapi-admin.js +0 -1
- package/strapi-server.js +0 -61
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, LinkButton } from "@strapi/design-system";
|
|
3
|
+
import { ExternalLink } from "@strapi/icons";
|
|
4
|
+
const PreviewImageInput = ({ attribute }) => {
|
|
5
|
+
const url = attribute?.options?.url;
|
|
6
|
+
if (!url) return null;
|
|
7
|
+
return /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
|
|
8
|
+
LinkButton,
|
|
9
|
+
{
|
|
10
|
+
href: url,
|
|
11
|
+
target: "_blank",
|
|
12
|
+
rel: "noreferrer",
|
|
13
|
+
variant: "tertiary",
|
|
14
|
+
size: "S",
|
|
15
|
+
endIcon: /* @__PURE__ */ jsx(ExternalLink, {}),
|
|
16
|
+
children: "Preview"
|
|
17
|
+
}
|
|
18
|
+
) });
|
|
19
|
+
};
|
|
20
|
+
export {
|
|
21
|
+
PreviewImageInput
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const designSystem = require("@strapi/design-system");
|
|
5
|
+
const icons = require("@strapi/icons");
|
|
6
|
+
const PreviewImageInput = ({ attribute }) => {
|
|
7
|
+
const url = attribute?.options?.url;
|
|
8
|
+
if (!url) return null;
|
|
9
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10
|
+
designSystem.LinkButton,
|
|
11
|
+
{
|
|
12
|
+
href: url,
|
|
13
|
+
target: "_blank",
|
|
14
|
+
rel: "noreferrer",
|
|
15
|
+
variant: "tertiary",
|
|
16
|
+
size: "S",
|
|
17
|
+
endIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, {}),
|
|
18
|
+
children: "Preview"
|
|
19
|
+
}
|
|
20
|
+
) });
|
|
21
|
+
};
|
|
22
|
+
exports.PreviewImageInput = PreviewImageInput;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const designSystem = require("@strapi/design-system");
|
|
5
|
+
const strapiAdmin = require("@strapi/content-manager/strapi-admin");
|
|
6
|
+
const admin = require("@strapi/strapi/admin");
|
|
7
|
+
const icons = require("@strapi/icons");
|
|
8
|
+
const collectPreviewItems = (value, attributes, componentSchemas, optionsMap) => {
|
|
9
|
+
const items = [];
|
|
10
|
+
if (!value || !attributes || typeof value !== "object") return items;
|
|
11
|
+
const pushItem = (componentUid, tempKey) => {
|
|
12
|
+
const opts = optionsMap[componentUid];
|
|
13
|
+
if (!opts) return;
|
|
14
|
+
const schema = componentSchemas[componentUid];
|
|
15
|
+
items.push({
|
|
16
|
+
uid: componentUid,
|
|
17
|
+
displayName: schema?.info?.displayName ?? componentUid,
|
|
18
|
+
previewUrl: opts.url,
|
|
19
|
+
previewName: opts.name,
|
|
20
|
+
count: 1,
|
|
21
|
+
tempKey
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
for (const [attributeName, attribute] of Object.entries(attributes)) {
|
|
25
|
+
const attributeValue = value[attributeName];
|
|
26
|
+
if (!attributeValue) continue;
|
|
27
|
+
if (attribute.type === "dynamiczone" && Array.isArray(attributeValue)) {
|
|
28
|
+
for (const item of attributeValue) {
|
|
29
|
+
if (!item || typeof item !== "object") continue;
|
|
30
|
+
const componentUid = item.__component;
|
|
31
|
+
const tempKey = item.__temp_key__;
|
|
32
|
+
if (componentUid) pushItem(componentUid, tempKey);
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (attribute.type === "component" && attribute.component) {
|
|
37
|
+
const componentUid = attribute.component;
|
|
38
|
+
if (attribute.repeatable && Array.isArray(attributeValue)) {
|
|
39
|
+
for (let i = 0; i < attributeValue.length; i++) {
|
|
40
|
+
const item = attributeValue[i];
|
|
41
|
+
pushItem(componentUid, item?.__temp_key__);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
pushItem(componentUid);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return items;
|
|
49
|
+
};
|
|
50
|
+
const ComponentPreviewPanel = () => {
|
|
51
|
+
const { components, contentType, isCreatingEntry } = strapiAdmin.unstable_useContentManagerContext();
|
|
52
|
+
const values = admin.useForm("ComponentPreviewPanel", (state) => state.values);
|
|
53
|
+
const { get } = admin.useFetchClient();
|
|
54
|
+
const [optionsMap, setOptionsMap] = react.useState({});
|
|
55
|
+
react.useEffect(() => {
|
|
56
|
+
let cancelled = false;
|
|
57
|
+
get("/api/component-preview-image/options").then(({ data }) => {
|
|
58
|
+
if (!cancelled) setOptionsMap(data ?? {});
|
|
59
|
+
}).catch(() => {
|
|
60
|
+
if (!cancelled) setOptionsMap({});
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
cancelled = true;
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
const previewItems = collectPreviewItems(
|
|
67
|
+
values,
|
|
68
|
+
contentType?.attributes,
|
|
69
|
+
components,
|
|
70
|
+
optionsMap
|
|
71
|
+
);
|
|
72
|
+
if (isCreatingEntry) {
|
|
73
|
+
return {
|
|
74
|
+
title: "Component previews",
|
|
75
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Save this entry once to load component previews." })
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (previewItems.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
title: "Component previews",
|
|
81
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "No component previews are available for this entry yet." })
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
title: "Component previews",
|
|
86
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 4, alignItems: "stretch", children: previewItems.map((item, index2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
87
|
+
designSystem.Box,
|
|
88
|
+
{
|
|
89
|
+
borderColor: "neutral200",
|
|
90
|
+
background: "neutral0",
|
|
91
|
+
hasRadius: true,
|
|
92
|
+
padding: 3,
|
|
93
|
+
shadow: "tableShadow",
|
|
94
|
+
width: "100%",
|
|
95
|
+
overflow: "hidden",
|
|
96
|
+
style: { boxSizing: "border-box" },
|
|
97
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, alignItems: "stretch", children: [
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
99
|
+
"img",
|
|
100
|
+
{
|
|
101
|
+
src: item.previewUrl,
|
|
102
|
+
alt: item.previewName || item.displayName,
|
|
103
|
+
style: {
|
|
104
|
+
width: "100%",
|
|
105
|
+
maxWidth: "100%",
|
|
106
|
+
display: "block",
|
|
107
|
+
borderRadius: "8px",
|
|
108
|
+
border: "1px solid #dcdce4",
|
|
109
|
+
objectFit: "cover",
|
|
110
|
+
boxSizing: "border-box"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
115
|
+
designSystem.Flex,
|
|
116
|
+
{
|
|
117
|
+
justifyContent: "space-between",
|
|
118
|
+
alignItems: "flex-start",
|
|
119
|
+
gap: 2,
|
|
120
|
+
width: "100%",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { minWidth: 0, flex: 1 }, children: [
|
|
123
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral800", children: item.previewName || item.displayName }),
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
125
|
+
designSystem.Typography,
|
|
126
|
+
{
|
|
127
|
+
variant: "pi",
|
|
128
|
+
textColor: "neutral600",
|
|
129
|
+
style: {
|
|
130
|
+
display: "block",
|
|
131
|
+
overflowWrap: "anywhere",
|
|
132
|
+
wordBreak: "break-word"
|
|
133
|
+
},
|
|
134
|
+
children: item.uid
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
139
|
+
designSystem.Button,
|
|
140
|
+
{
|
|
141
|
+
variant: "tertiary",
|
|
142
|
+
size: "S",
|
|
143
|
+
tag: "a",
|
|
144
|
+
href: item.previewUrl,
|
|
145
|
+
target: "_blank",
|
|
146
|
+
rel: "noreferrer",
|
|
147
|
+
endIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, {}),
|
|
148
|
+
style: { flexShrink: 0 },
|
|
149
|
+
children: "Open"
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
] })
|
|
156
|
+
},
|
|
157
|
+
item.tempKey ?? `${index2}-${item.uid}`
|
|
158
|
+
)) })
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
const index = {
|
|
162
|
+
register(app) {
|
|
163
|
+
app.customFields.register({
|
|
164
|
+
name: "preview-image",
|
|
165
|
+
pluginId: "component-preview-image",
|
|
166
|
+
type: "string",
|
|
167
|
+
inputSize: { default: 12, isResizable: false },
|
|
168
|
+
intlLabel: {
|
|
169
|
+
id: "component-preview-image.preview-image.label",
|
|
170
|
+
defaultMessage: "Preview Image"
|
|
171
|
+
},
|
|
172
|
+
intlDescription: {
|
|
173
|
+
id: "component-preview-image.preview-image.description",
|
|
174
|
+
defaultMessage: "Schema-level preview image — set the image URL once in the Content-Type Builder, shown in the edit-view side panel"
|
|
175
|
+
},
|
|
176
|
+
components: {
|
|
177
|
+
Input: async () => Promise.resolve().then(() => require("../_chunks/PreviewImageInput-Bha74a3E.js")).then((mod) => ({
|
|
178
|
+
default: mod.PreviewImageInput
|
|
179
|
+
}))
|
|
180
|
+
},
|
|
181
|
+
options: {
|
|
182
|
+
base: [
|
|
183
|
+
{
|
|
184
|
+
name: "options.url",
|
|
185
|
+
type: "text",
|
|
186
|
+
intlLabel: {
|
|
187
|
+
id: "component-preview-image.options.url.label",
|
|
188
|
+
defaultMessage: "Preview Image URL"
|
|
189
|
+
},
|
|
190
|
+
description: {
|
|
191
|
+
id: "component-preview-image.options.url.description",
|
|
192
|
+
defaultMessage: "Direct URL of the image to display in the preview panel"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
bootstrap(app) {
|
|
200
|
+
app.getPlugin("content-manager").apis.addEditViewSidePanel([ComponentPreviewPanel]);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
module.exports = index;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Typography, Flex, Box, Button } from "@strapi/design-system";
|
|
4
|
+
import { unstable_useContentManagerContext } from "@strapi/content-manager/strapi-admin";
|
|
5
|
+
import { useForm, useFetchClient } from "@strapi/strapi/admin";
|
|
6
|
+
import { ExternalLink } from "@strapi/icons";
|
|
7
|
+
const collectPreviewItems = (value, attributes, componentSchemas, optionsMap) => {
|
|
8
|
+
const items = [];
|
|
9
|
+
if (!value || !attributes || typeof value !== "object") return items;
|
|
10
|
+
const pushItem = (componentUid, tempKey) => {
|
|
11
|
+
const opts = optionsMap[componentUid];
|
|
12
|
+
if (!opts) return;
|
|
13
|
+
const schema = componentSchemas[componentUid];
|
|
14
|
+
items.push({
|
|
15
|
+
uid: componentUid,
|
|
16
|
+
displayName: schema?.info?.displayName ?? componentUid,
|
|
17
|
+
previewUrl: opts.url,
|
|
18
|
+
previewName: opts.name,
|
|
19
|
+
count: 1,
|
|
20
|
+
tempKey
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
for (const [attributeName, attribute] of Object.entries(attributes)) {
|
|
24
|
+
const attributeValue = value[attributeName];
|
|
25
|
+
if (!attributeValue) continue;
|
|
26
|
+
if (attribute.type === "dynamiczone" && Array.isArray(attributeValue)) {
|
|
27
|
+
for (const item of attributeValue) {
|
|
28
|
+
if (!item || typeof item !== "object") continue;
|
|
29
|
+
const componentUid = item.__component;
|
|
30
|
+
const tempKey = item.__temp_key__;
|
|
31
|
+
if (componentUid) pushItem(componentUid, tempKey);
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (attribute.type === "component" && attribute.component) {
|
|
36
|
+
const componentUid = attribute.component;
|
|
37
|
+
if (attribute.repeatable && Array.isArray(attributeValue)) {
|
|
38
|
+
for (let i = 0; i < attributeValue.length; i++) {
|
|
39
|
+
const item = attributeValue[i];
|
|
40
|
+
pushItem(componentUid, item?.__temp_key__);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
pushItem(componentUid);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return items;
|
|
48
|
+
};
|
|
49
|
+
const ComponentPreviewPanel = () => {
|
|
50
|
+
const { components, contentType, isCreatingEntry } = unstable_useContentManagerContext();
|
|
51
|
+
const values = useForm("ComponentPreviewPanel", (state) => state.values);
|
|
52
|
+
const { get } = useFetchClient();
|
|
53
|
+
const [optionsMap, setOptionsMap] = useState({});
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
get("/api/component-preview-image/options").then(({ data }) => {
|
|
57
|
+
if (!cancelled) setOptionsMap(data ?? {});
|
|
58
|
+
}).catch(() => {
|
|
59
|
+
if (!cancelled) setOptionsMap({});
|
|
60
|
+
});
|
|
61
|
+
return () => {
|
|
62
|
+
cancelled = true;
|
|
63
|
+
};
|
|
64
|
+
}, []);
|
|
65
|
+
const previewItems = collectPreviewItems(
|
|
66
|
+
values,
|
|
67
|
+
contentType?.attributes,
|
|
68
|
+
components,
|
|
69
|
+
optionsMap
|
|
70
|
+
);
|
|
71
|
+
if (isCreatingEntry) {
|
|
72
|
+
return {
|
|
73
|
+
title: "Component previews",
|
|
74
|
+
content: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "Save this entry once to load component previews." })
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (previewItems.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
title: "Component previews",
|
|
80
|
+
content: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "No component previews are available for this entry yet." })
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
title: "Component previews",
|
|
85
|
+
content: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 4, alignItems: "stretch", children: previewItems.map((item, index2) => /* @__PURE__ */ jsx(
|
|
86
|
+
Box,
|
|
87
|
+
{
|
|
88
|
+
borderColor: "neutral200",
|
|
89
|
+
background: "neutral0",
|
|
90
|
+
hasRadius: true,
|
|
91
|
+
padding: 3,
|
|
92
|
+
shadow: "tableShadow",
|
|
93
|
+
width: "100%",
|
|
94
|
+
overflow: "hidden",
|
|
95
|
+
style: { boxSizing: "border-box" },
|
|
96
|
+
children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, alignItems: "stretch", children: [
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
"img",
|
|
99
|
+
{
|
|
100
|
+
src: item.previewUrl,
|
|
101
|
+
alt: item.previewName || item.displayName,
|
|
102
|
+
style: {
|
|
103
|
+
width: "100%",
|
|
104
|
+
maxWidth: "100%",
|
|
105
|
+
display: "block",
|
|
106
|
+
borderRadius: "8px",
|
|
107
|
+
border: "1px solid #dcdce4",
|
|
108
|
+
objectFit: "cover",
|
|
109
|
+
boxSizing: "border-box"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ jsxs(
|
|
114
|
+
Flex,
|
|
115
|
+
{
|
|
116
|
+
justifyContent: "space-between",
|
|
117
|
+
alignItems: "flex-start",
|
|
118
|
+
gap: 2,
|
|
119
|
+
width: "100%",
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsxs(Box, { style: { minWidth: 0, flex: 1 }, children: [
|
|
122
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral800", children: item.previewName || item.displayName }),
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
Typography,
|
|
125
|
+
{
|
|
126
|
+
variant: "pi",
|
|
127
|
+
textColor: "neutral600",
|
|
128
|
+
style: {
|
|
129
|
+
display: "block",
|
|
130
|
+
overflowWrap: "anywhere",
|
|
131
|
+
wordBreak: "break-word"
|
|
132
|
+
},
|
|
133
|
+
children: item.uid
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsx(
|
|
138
|
+
Button,
|
|
139
|
+
{
|
|
140
|
+
variant: "tertiary",
|
|
141
|
+
size: "S",
|
|
142
|
+
tag: "a",
|
|
143
|
+
href: item.previewUrl,
|
|
144
|
+
target: "_blank",
|
|
145
|
+
rel: "noreferrer",
|
|
146
|
+
endIcon: /* @__PURE__ */ jsx(ExternalLink, {}),
|
|
147
|
+
style: { flexShrink: 0 },
|
|
148
|
+
children: "Open"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] })
|
|
155
|
+
},
|
|
156
|
+
item.tempKey ?? `${index2}-${item.uid}`
|
|
157
|
+
)) })
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const index = {
|
|
161
|
+
register(app) {
|
|
162
|
+
app.customFields.register({
|
|
163
|
+
name: "preview-image",
|
|
164
|
+
pluginId: "component-preview-image",
|
|
165
|
+
type: "string",
|
|
166
|
+
inputSize: { default: 12, isResizable: false },
|
|
167
|
+
intlLabel: {
|
|
168
|
+
id: "component-preview-image.preview-image.label",
|
|
169
|
+
defaultMessage: "Preview Image"
|
|
170
|
+
},
|
|
171
|
+
intlDescription: {
|
|
172
|
+
id: "component-preview-image.preview-image.description",
|
|
173
|
+
defaultMessage: "Schema-level preview image — set the image URL once in the Content-Type Builder, shown in the edit-view side panel"
|
|
174
|
+
},
|
|
175
|
+
components: {
|
|
176
|
+
Input: async () => import("../_chunks/PreviewImageInput-BcPJkyKS.mjs").then((mod) => ({
|
|
177
|
+
default: mod.PreviewImageInput
|
|
178
|
+
}))
|
|
179
|
+
},
|
|
180
|
+
options: {
|
|
181
|
+
base: [
|
|
182
|
+
{
|
|
183
|
+
name: "options.url",
|
|
184
|
+
type: "text",
|
|
185
|
+
intlLabel: {
|
|
186
|
+
id: "component-preview-image.options.url.label",
|
|
187
|
+
defaultMessage: "Preview Image URL"
|
|
188
|
+
},
|
|
189
|
+
description: {
|
|
190
|
+
id: "component-preview-image.options.url.description",
|
|
191
|
+
defaultMessage: "Direct URL of the image to display in the preview panel"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
bootstrap(app) {
|
|
199
|
+
app.getPlugin("content-manager").apis.addEditViewSidePanel([ComponentPreviewPanel]);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
export {
|
|
203
|
+
index as default
|
|
204
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const CUSTOM_FIELD_KEY = "plugin::component-preview-image.preview-image";
|
|
3
|
+
const optionsController = ({ strapi }) => ({
|
|
4
|
+
async getOptions(ctx) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const [uid, schema] of Object.entries(strapi.components)) {
|
|
7
|
+
for (const attr of Object.values(schema.attributes)) {
|
|
8
|
+
if (attr.type === "customField" && attr.customField === CUSTOM_FIELD_KEY && attr.options?.url) {
|
|
9
|
+
result[uid] = {
|
|
10
|
+
name: schema.info?.displayName || uid,
|
|
11
|
+
url: attr.options.url
|
|
12
|
+
};
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
ctx.body = result;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const PLUGIN_NAME = "component-preview-image";
|
|
21
|
+
const index = {
|
|
22
|
+
register({ strapi }) {
|
|
23
|
+
strapi.customFields.register({
|
|
24
|
+
name: "preview-image",
|
|
25
|
+
plugin: PLUGIN_NAME,
|
|
26
|
+
type: "string"
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
bootstrap() {
|
|
30
|
+
},
|
|
31
|
+
destroy() {
|
|
32
|
+
},
|
|
33
|
+
config: {},
|
|
34
|
+
routes: {
|
|
35
|
+
"content-api": {
|
|
36
|
+
type: "content-api",
|
|
37
|
+
routes: [
|
|
38
|
+
{
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/options",
|
|
41
|
+
handler: "options.getOptions",
|
|
42
|
+
config: {
|
|
43
|
+
auth: false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
controllers: {
|
|
50
|
+
options: optionsController
|
|
51
|
+
},
|
|
52
|
+
services: {},
|
|
53
|
+
policies: {},
|
|
54
|
+
middlewares: {}
|
|
55
|
+
};
|
|
56
|
+
module.exports = index;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const CUSTOM_FIELD_KEY = "plugin::component-preview-image.preview-image";
|
|
2
|
+
const optionsController = ({ strapi }) => ({
|
|
3
|
+
async getOptions(ctx) {
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const [uid, schema] of Object.entries(strapi.components)) {
|
|
6
|
+
for (const attr of Object.values(schema.attributes)) {
|
|
7
|
+
if (attr.type === "customField" && attr.customField === CUSTOM_FIELD_KEY && attr.options?.url) {
|
|
8
|
+
result[uid] = {
|
|
9
|
+
name: schema.info?.displayName || uid,
|
|
10
|
+
url: attr.options.url
|
|
11
|
+
};
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
ctx.body = result;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const PLUGIN_NAME = "component-preview-image";
|
|
20
|
+
const index = {
|
|
21
|
+
register({ strapi }) {
|
|
22
|
+
strapi.customFields.register({
|
|
23
|
+
name: "preview-image",
|
|
24
|
+
plugin: PLUGIN_NAME,
|
|
25
|
+
type: "string"
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
bootstrap() {
|
|
29
|
+
},
|
|
30
|
+
destroy() {
|
|
31
|
+
},
|
|
32
|
+
config: {},
|
|
33
|
+
routes: {
|
|
34
|
+
"content-api": {
|
|
35
|
+
type: "content-api",
|
|
36
|
+
routes: [
|
|
37
|
+
{
|
|
38
|
+
method: "GET",
|
|
39
|
+
path: "/options",
|
|
40
|
+
handler: "options.getOptions",
|
|
41
|
+
config: {
|
|
42
|
+
auth: false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
controllers: {
|
|
49
|
+
options: optionsController
|
|
50
|
+
},
|
|
51
|
+
services: {},
|
|
52
|
+
policies: {},
|
|
53
|
+
middlewares: {}
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
index as default
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devx-labs/strapi-preview",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Strapi 5 plugin that adds a preview-image custom field (configured per component in the Content-Type Builder with a direct image URL) and renders a side panel in the edit view showing each component's preview image in dynamic-zone order.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"custom-field",
|
|
12
12
|
"side-panel"
|
|
13
13
|
],
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
},
|
|
14
|
+
"type": "commonjs",
|
|
15
|
+
"author": "Pratham Bhatia",
|
|
17
16
|
"license": "MIT",
|
|
18
17
|
"repository": {
|
|
19
18
|
"type": "git",
|
|
@@ -23,33 +22,51 @@
|
|
|
23
22
|
"bugs": {
|
|
24
23
|
"url": "https://github.com/prathambdevx/strapi-component-preview/issues"
|
|
25
24
|
},
|
|
26
|
-
"main": "./strapi-server.js",
|
|
27
25
|
"exports": {
|
|
28
26
|
"./package.json": "./package.json",
|
|
29
27
|
"./strapi-admin": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"source": "./admin/src/index.tsx",
|
|
29
|
+
"import": "./dist/admin/index.mjs",
|
|
30
|
+
"require": "./dist/admin/index.js",
|
|
31
|
+
"default": "./dist/admin/index.js"
|
|
32
32
|
},
|
|
33
33
|
"./strapi-server": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
"source": "./server/src/index.ts",
|
|
35
|
+
"import": "./dist/server/index.mjs",
|
|
36
|
+
"require": "./dist/server/index.js",
|
|
37
|
+
"default": "./dist/server/index.js"
|
|
38
|
+
}
|
|
38
39
|
},
|
|
39
40
|
"files": [
|
|
40
|
-
"
|
|
41
|
-
"server",
|
|
42
|
-
"strapi-admin.js",
|
|
43
|
-
"strapi-server.js",
|
|
41
|
+
"dist",
|
|
44
42
|
"README.md",
|
|
45
43
|
"LICENSE"
|
|
46
44
|
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "strapi-plugin build",
|
|
47
|
+
"watch": "strapi-plugin watch",
|
|
48
|
+
"verify": "strapi-plugin verify"
|
|
49
|
+
},
|
|
47
50
|
"strapi": {
|
|
48
51
|
"name": "component-preview-image",
|
|
49
52
|
"displayName": "Strapi Preview",
|
|
50
53
|
"description": "Adds a preview-image custom field (URL configured in CTB) and an edit-view side panel that displays those images.",
|
|
51
54
|
"kind": "plugin"
|
|
52
55
|
},
|
|
56
|
+
"dependencies": {},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@strapi/sdk-plugin": "^5.3.2",
|
|
59
|
+
"@strapi/strapi": "^5.0.0",
|
|
60
|
+
"@strapi/design-system": "^2.0.0-rc.21",
|
|
61
|
+
"@strapi/icons": "^2.0.0-rc.21",
|
|
62
|
+
"@types/react": "^18.0.0",
|
|
63
|
+
"@types/react-dom": "^18.0.0",
|
|
64
|
+
"react": "^18.0.0",
|
|
65
|
+
"react-dom": "^18.0.0",
|
|
66
|
+
"react-router-dom": "^6.0.0",
|
|
67
|
+
"styled-components": "^6.0.0",
|
|
68
|
+
"typescript": "^5.0.0"
|
|
69
|
+
},
|
|
53
70
|
"peerDependencies": {
|
|
54
71
|
"@strapi/content-manager": ">=5.0.0",
|
|
55
72
|
"@strapi/design-system": ">=2.0.0-0",
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { Box, Button, Flex, Typography } from '@strapi/design-system';
|
|
3
|
-
import type { PanelComponent } from '@strapi/content-manager/strapi-admin';
|
|
4
|
-
import { unstable_useContentManagerContext } from '@strapi/content-manager/strapi-admin';
|
|
5
|
-
import { useForm, useFetchClient } from '@strapi/strapi/admin';
|
|
6
|
-
import { ExternalLink } from '@strapi/icons';
|
|
7
|
-
|
|
8
|
-
type SchemaAttribute = {
|
|
9
|
-
type?: string;
|
|
10
|
-
component?: string;
|
|
11
|
-
repeatable?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
type SchemaDefinition = {
|
|
15
|
-
attributes?: Record<string, SchemaAttribute>;
|
|
16
|
-
info?: { displayName?: string };
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type PreviewOption = { name: string; url: string };
|
|
20
|
-
|
|
21
|
-
type PreviewItem = {
|
|
22
|
-
uid: string;
|
|
23
|
-
displayName: string;
|
|
24
|
-
previewUrl: string;
|
|
25
|
-
previewName: string;
|
|
26
|
-
count: number;
|
|
27
|
-
tempKey?: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const collectPreviewItems = (
|
|
31
|
-
value: unknown,
|
|
32
|
-
attributes: Record<string, SchemaAttribute> | undefined,
|
|
33
|
-
componentSchemas: Record<string, SchemaDefinition>,
|
|
34
|
-
optionsMap: Record<string, PreviewOption>
|
|
35
|
-
): PreviewItem[] => {
|
|
36
|
-
const items: PreviewItem[] = [];
|
|
37
|
-
|
|
38
|
-
if (!value || !attributes || typeof value !== 'object') return items;
|
|
39
|
-
|
|
40
|
-
const pushItem = (componentUid: string, tempKey?: string) => {
|
|
41
|
-
const opts = optionsMap[componentUid];
|
|
42
|
-
if (!opts) return;
|
|
43
|
-
const schema = componentSchemas[componentUid];
|
|
44
|
-
items.push({
|
|
45
|
-
uid: componentUid,
|
|
46
|
-
displayName: schema?.info?.displayName ?? componentUid,
|
|
47
|
-
previewUrl: opts.url,
|
|
48
|
-
previewName: opts.name,
|
|
49
|
-
count: 1,
|
|
50
|
-
tempKey,
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
for (const [attributeName, attribute] of Object.entries(attributes)) {
|
|
55
|
-
const attributeValue = (value as Record<string, unknown>)[attributeName];
|
|
56
|
-
if (!attributeValue) continue;
|
|
57
|
-
|
|
58
|
-
if (attribute.type === 'dynamiczone' && Array.isArray(attributeValue)) {
|
|
59
|
-
for (const item of attributeValue) {
|
|
60
|
-
if (!item || typeof item !== 'object') continue;
|
|
61
|
-
const componentUid = (item as { __component?: string }).__component;
|
|
62
|
-
const tempKey = (item as { __temp_key__?: string }).__temp_key__;
|
|
63
|
-
if (componentUid) pushItem(componentUid, tempKey);
|
|
64
|
-
}
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (attribute.type === 'component' && attribute.component) {
|
|
69
|
-
const componentUid = attribute.component;
|
|
70
|
-
if (attribute.repeatable && Array.isArray(attributeValue)) {
|
|
71
|
-
for (let i = 0; i < attributeValue.length; i++) {
|
|
72
|
-
const item = attributeValue[i] as { __temp_key__?: string } | undefined;
|
|
73
|
-
pushItem(componentUid, item?.__temp_key__);
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
pushItem(componentUid);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return items;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export const ComponentPreviewPanel: PanelComponent = () => {
|
|
85
|
-
const { components, contentType, isCreatingEntry } = unstable_useContentManagerContext();
|
|
86
|
-
const values = useForm('ComponentPreviewPanel', (state) => state.values);
|
|
87
|
-
const { get } = useFetchClient();
|
|
88
|
-
const [optionsMap, setOptionsMap] = useState<Record<string, PreviewOption>>({});
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
let cancelled = false;
|
|
92
|
-
|
|
93
|
-
get('/api/component-preview-image/options')
|
|
94
|
-
.then(({ data }: { data: Record<string, PreviewOption> }) => {
|
|
95
|
-
if (!cancelled) setOptionsMap(data ?? {});
|
|
96
|
-
})
|
|
97
|
-
.catch(() => {
|
|
98
|
-
if (!cancelled) setOptionsMap({});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
cancelled = true;
|
|
103
|
-
};
|
|
104
|
-
}, []);
|
|
105
|
-
|
|
106
|
-
const previewItems = collectPreviewItems(
|
|
107
|
-
values,
|
|
108
|
-
contentType?.attributes,
|
|
109
|
-
components as Record<string, SchemaDefinition>,
|
|
110
|
-
optionsMap
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (isCreatingEntry) {
|
|
114
|
-
return {
|
|
115
|
-
title: 'Component previews',
|
|
116
|
-
content: (
|
|
117
|
-
<Typography variant="omega" textColor="neutral600">
|
|
118
|
-
Save this entry once to load component previews.
|
|
119
|
-
</Typography>
|
|
120
|
-
),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (previewItems.length === 0) {
|
|
125
|
-
return {
|
|
126
|
-
title: 'Component previews',
|
|
127
|
-
content: (
|
|
128
|
-
<Typography variant="omega" textColor="neutral600">
|
|
129
|
-
No component previews are available for this entry yet.
|
|
130
|
-
</Typography>
|
|
131
|
-
),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
title: 'Component previews',
|
|
137
|
-
content: (
|
|
138
|
-
<Flex direction="column" gap={4} alignItems="stretch">
|
|
139
|
-
{previewItems.map((item, index) => (
|
|
140
|
-
<Box
|
|
141
|
-
key={item.tempKey ?? `${index}-${item.uid}`}
|
|
142
|
-
borderColor="neutral200"
|
|
143
|
-
background="neutral0"
|
|
144
|
-
hasRadius
|
|
145
|
-
padding={3}
|
|
146
|
-
shadow="tableShadow"
|
|
147
|
-
width="100%"
|
|
148
|
-
overflow="hidden"
|
|
149
|
-
style={{ boxSizing: 'border-box' }}
|
|
150
|
-
>
|
|
151
|
-
<Flex direction="column" gap={3} alignItems="stretch">
|
|
152
|
-
<img
|
|
153
|
-
src={item.previewUrl}
|
|
154
|
-
alt={item.previewName || item.displayName}
|
|
155
|
-
style={{
|
|
156
|
-
width: '100%',
|
|
157
|
-
maxWidth: '100%',
|
|
158
|
-
display: 'block',
|
|
159
|
-
borderRadius: '8px',
|
|
160
|
-
border: '1px solid #dcdce4',
|
|
161
|
-
objectFit: 'cover',
|
|
162
|
-
boxSizing: 'border-box',
|
|
163
|
-
}}
|
|
164
|
-
/>
|
|
165
|
-
<Flex
|
|
166
|
-
justifyContent="space-between"
|
|
167
|
-
alignItems="flex-start"
|
|
168
|
-
gap={2}
|
|
169
|
-
width="100%"
|
|
170
|
-
>
|
|
171
|
-
<Box style={{ minWidth: 0, flex: 1 }}>
|
|
172
|
-
<Typography variant="sigma" textColor="neutral800">
|
|
173
|
-
{item.previewName || item.displayName}
|
|
174
|
-
</Typography>
|
|
175
|
-
<Typography
|
|
176
|
-
variant="pi"
|
|
177
|
-
textColor="neutral600"
|
|
178
|
-
style={{
|
|
179
|
-
display: 'block',
|
|
180
|
-
overflowWrap: 'anywhere',
|
|
181
|
-
wordBreak: 'break-word',
|
|
182
|
-
}}
|
|
183
|
-
>
|
|
184
|
-
{item.uid}
|
|
185
|
-
</Typography>
|
|
186
|
-
</Box>
|
|
187
|
-
<Button
|
|
188
|
-
variant="tertiary"
|
|
189
|
-
size="S"
|
|
190
|
-
tag="a"
|
|
191
|
-
href={item.previewUrl}
|
|
192
|
-
target="_blank"
|
|
193
|
-
rel="noreferrer"
|
|
194
|
-
endIcon={<ExternalLink />}
|
|
195
|
-
style={{ flexShrink: 0 }}
|
|
196
|
-
>
|
|
197
|
-
Open
|
|
198
|
-
</Button>
|
|
199
|
-
</Flex>
|
|
200
|
-
</Flex>
|
|
201
|
-
</Box>
|
|
202
|
-
))}
|
|
203
|
-
</Flex>
|
|
204
|
-
),
|
|
205
|
-
};
|
|
206
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Flex, LinkButton } from '@strapi/design-system';
|
|
2
|
-
import { ExternalLink } from '@strapi/icons';
|
|
3
|
-
|
|
4
|
-
type PreviewImageInputProps = {
|
|
5
|
-
attribute?: {
|
|
6
|
-
options?: {
|
|
7
|
-
url?: string;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const PreviewImageInput = ({ attribute }: PreviewImageInputProps) => {
|
|
13
|
-
const url = attribute?.options?.url;
|
|
14
|
-
|
|
15
|
-
if (!url) return null;
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<Flex justifyContent="flex-end">
|
|
19
|
-
<LinkButton
|
|
20
|
-
href={url}
|
|
21
|
-
target="_blank"
|
|
22
|
-
rel="noreferrer"
|
|
23
|
-
variant="tertiary"
|
|
24
|
-
size="S"
|
|
25
|
-
endIcon={<ExternalLink />}
|
|
26
|
-
>
|
|
27
|
-
Preview
|
|
28
|
-
</LinkButton>
|
|
29
|
-
</Flex>
|
|
30
|
-
);
|
|
31
|
-
};
|
package/admin/src/index.tsx
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { ComponentPreviewPanel } from './components/ComponentPreviewPanel';
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
register(app: any) {
|
|
5
|
-
app.customFields.register({
|
|
6
|
-
name: 'preview-image',
|
|
7
|
-
pluginId: 'component-preview-image',
|
|
8
|
-
type: 'string',
|
|
9
|
-
inputSize: { default: 12, isResizable: false },
|
|
10
|
-
intlLabel: {
|
|
11
|
-
id: 'component-preview-image.preview-image.label',
|
|
12
|
-
defaultMessage: 'Preview Image',
|
|
13
|
-
},
|
|
14
|
-
intlDescription: {
|
|
15
|
-
id: 'component-preview-image.preview-image.description',
|
|
16
|
-
defaultMessage:
|
|
17
|
-
'Schema-level preview image — set the image URL once in the Content-Type Builder, shown in the edit-view side panel',
|
|
18
|
-
},
|
|
19
|
-
components: {
|
|
20
|
-
Input: async () =>
|
|
21
|
-
import('./components/PreviewImageInput').then((mod) => ({
|
|
22
|
-
default: mod.PreviewImageInput,
|
|
23
|
-
})),
|
|
24
|
-
},
|
|
25
|
-
options: {
|
|
26
|
-
base: [
|
|
27
|
-
{
|
|
28
|
-
name: 'options.url',
|
|
29
|
-
type: 'text',
|
|
30
|
-
intlLabel: {
|
|
31
|
-
id: 'component-preview-image.options.url.label',
|
|
32
|
-
defaultMessage: 'Preview Image URL',
|
|
33
|
-
},
|
|
34
|
-
description: {
|
|
35
|
-
id: 'component-preview-image.options.url.description',
|
|
36
|
-
defaultMessage: 'Direct URL of the image to display in the preview panel',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
bootstrap(app: any) {
|
|
45
|
-
app.getPlugin('content-manager').apis.addEditViewSidePanel([ComponentPreviewPanel]);
|
|
46
|
-
},
|
|
47
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Core } from '@strapi/strapi';
|
|
2
|
-
|
|
3
|
-
const CUSTOM_FIELD_KEY = 'plugin::component-preview-image.preview-image';
|
|
4
|
-
|
|
5
|
-
export default ({ strapi }: { strapi: Core.Strapi }) => ({
|
|
6
|
-
async getOptions(ctx: any) {
|
|
7
|
-
const result: Record<string, { name: string; url: string }> = {};
|
|
8
|
-
|
|
9
|
-
for (const [uid, schema] of Object.entries(strapi.components as Record<string, any>)) {
|
|
10
|
-
for (const attr of Object.values(schema.attributes as Record<string, any>)) {
|
|
11
|
-
if (
|
|
12
|
-
attr.type === 'customField' &&
|
|
13
|
-
attr.customField === CUSTOM_FIELD_KEY &&
|
|
14
|
-
attr.options?.url
|
|
15
|
-
) {
|
|
16
|
-
result[uid] = {
|
|
17
|
-
name: schema.info?.displayName || uid,
|
|
18
|
-
url: attr.options.url,
|
|
19
|
-
};
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
ctx.body = result;
|
|
26
|
-
},
|
|
27
|
-
});
|
package/server/src/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Core } from '@strapi/strapi';
|
|
2
|
-
import optionsController from './controllers/options';
|
|
3
|
-
|
|
4
|
-
const PLUGIN_NAME = 'component-preview-image';
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
register({ strapi }: { strapi: Core.Strapi }) {
|
|
8
|
-
strapi.customFields.register({
|
|
9
|
-
name: 'preview-image',
|
|
10
|
-
plugin: PLUGIN_NAME,
|
|
11
|
-
type: 'string',
|
|
12
|
-
});
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
bootstrap() {},
|
|
16
|
-
destroy() {},
|
|
17
|
-
|
|
18
|
-
config: {},
|
|
19
|
-
|
|
20
|
-
routes: {
|
|
21
|
-
'content-api': {
|
|
22
|
-
type: 'content-api',
|
|
23
|
-
routes: [
|
|
24
|
-
{
|
|
25
|
-
method: 'GET',
|
|
26
|
-
path: '/options',
|
|
27
|
-
handler: 'options.getOptions',
|
|
28
|
-
config: {
|
|
29
|
-
auth: false,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
controllers: {
|
|
37
|
-
options: optionsController,
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
services: {},
|
|
41
|
-
policies: {},
|
|
42
|
-
middlewares: {},
|
|
43
|
-
};
|
package/strapi-admin.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './admin/src/index';
|
package/strapi-server.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const CUSTOM_FIELD_KEY = 'plugin::component-preview-image.preview-image';
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
register({ strapi }) {
|
|
7
|
-
strapi.customFields.register({
|
|
8
|
-
name: 'preview-image',
|
|
9
|
-
plugin: 'component-preview-image',
|
|
10
|
-
type: 'string',
|
|
11
|
-
inputSize: { default: 12, isResizable: false },
|
|
12
|
-
});
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
bootstrap() {},
|
|
16
|
-
destroy() {},
|
|
17
|
-
config: {},
|
|
18
|
-
|
|
19
|
-
routes: {
|
|
20
|
-
'content-api': {
|
|
21
|
-
type: 'content-api',
|
|
22
|
-
routes: [
|
|
23
|
-
{
|
|
24
|
-
method: 'GET',
|
|
25
|
-
path: '/options',
|
|
26
|
-
handler: 'options.getOptions',
|
|
27
|
-
config: { auth: false },
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
controllers: {
|
|
34
|
-
options: ({ strapi }) => ({
|
|
35
|
-
async getOptions(ctx) {
|
|
36
|
-
const result = {};
|
|
37
|
-
|
|
38
|
-
for (const [uid, schema] of Object.entries(strapi.components || {})) {
|
|
39
|
-
for (const attr of Object.values(schema.attributes || {})) {
|
|
40
|
-
if (
|
|
41
|
-
attr.customField === CUSTOM_FIELD_KEY &&
|
|
42
|
-
attr.options?.url
|
|
43
|
-
) {
|
|
44
|
-
result[uid] = {
|
|
45
|
-
name: schema.info?.displayName || uid,
|
|
46
|
-
url: attr.options.url,
|
|
47
|
-
};
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
ctx.body = result;
|
|
54
|
-
},
|
|
55
|
-
}),
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
services: {},
|
|
59
|
-
policies: {},
|
|
60
|
-
middlewares: {},
|
|
61
|
-
};
|