@uptechworks/url-preview-field 0.1.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.
- package/README.md +73 -0
- package/dist/_chunks/UrlFieldInput-BhujW-Fo.js +173 -0
- package/dist/_chunks/UrlFieldInput-D0OIBuvW.mjs +156 -0
- package/dist/_chunks/en-B4KWt_jN.js +4 -0
- package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
- package/dist/admin/index.js +86 -0
- package/dist/admin/index.mjs +87 -0
- package/dist/admin/src/components/Initializer.d.ts +5 -0
- package/dist/admin/src/components/PluginIcon.d.ts +2 -0
- package/dist/admin/src/components/UrlFieldIcon.d.ts +1 -0
- package/dist/admin/src/components/UrlFieldInput.d.ts +14 -0
- package/dist/admin/src/index.d.ts +10 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/getTranslation.d.ts +2 -0
- package/dist/server/index.js +141 -0
- package/dist/server/index.mjs +122 -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 +8 -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 +67 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/pluginId.d.ts +1 -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/index.d.ts +18 -0
- package/dist/server/src/services/index.d.ts +21 -0
- package/dist/server/src/services/service.d.ts +20 -0
- package/package.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# url-preview-field
|
|
2
|
+
|
|
3
|
+
A custom field plugin for Strapi that allows you to preview a URL with a social share card, similar to what you see when sharing a link on social media.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Custom URL Field:** Adds a new custom field type (`url`) to Strapi content types.
|
|
8
|
+
- **Live Preview:** When entering a URL, the field fetches and displays a preview card with the page’s title, image, and domain.
|
|
9
|
+
- **Social Card Style:** The preview mimics the appearance of social media link previews.
|
|
10
|
+
- **Advanced Settings:** Optionally mark the field as required in the content type builder (UI only).
|
|
11
|
+
- **Backend Metadata Fetching:** Uses a Strapi backend route to fetch Open Graph and Twitter card metadata for the given URL.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# In your Strapi project root
|
|
17
|
+
npm install url-preview-field
|
|
18
|
+
# or
|
|
19
|
+
yarn add url-preview-field
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You will need to add the following to your `config/middlewares.ts` to ensure the image from the URL can be displayed. In the img-src add the nessecary domains, otherwise Strapi will not display the image.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
{
|
|
26
|
+
name: 'strapi::security',
|
|
27
|
+
config: {
|
|
28
|
+
contentSecurityPolicy: {
|
|
29
|
+
directives: {
|
|
30
|
+
'script-src': ["'self'", "'unsafe-inline'"],
|
|
31
|
+
'img-src': ["'self'", 'data:', 'strapi.io'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then, enable the plugin in your Strapi project as you would with any other plugin.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
1. **Add the Field to a Content Type:**
|
|
43
|
+
- In the Strapi admin panel, edit or create a content type.
|
|
44
|
+
- Add a new field, select Custom then select the “URL” custom field (provided by this plugin).
|
|
45
|
+
|
|
46
|
+
2. **Enter a URL:**
|
|
47
|
+
- In the content manager, enter a URL in the field.
|
|
48
|
+
- The plugin will fetch and display a preview card with the page’s metadata (title, image, domain, etc.).
|
|
49
|
+
|
|
50
|
+
## How It Works
|
|
51
|
+
|
|
52
|
+
- **Admin UI:**
|
|
53
|
+
The plugin registers a custom field with a React component that handles user input and displays the preview card.
|
|
54
|
+
- **Backend:**
|
|
55
|
+
The plugin exposes a POST endpoint (`/url-preview-field/url-metadata`) that fetches the target URL, parses its HTML for Open Graph/Twitter metadata, and returns the relevant information to the frontend.
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
### Scripts
|
|
60
|
+
|
|
61
|
+
- `npm run build` – Build the plugin for production.
|
|
62
|
+
- `npm run watch` – Watch for changes and rebuild automatically.
|
|
63
|
+
- `npm run verify` – Verify the plugin.
|
|
64
|
+
- `npm run test:ts:front` – Type-check the admin (frontend) code.
|
|
65
|
+
- `npm run test:ts:back` – Type-check the server (backend) code.
|
|
66
|
+
|
|
67
|
+
## Limitations
|
|
68
|
+
|
|
69
|
+
- The plugin fetches metadata using the backend; the server must be able to access the target URLs.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const designSystem = require("@strapi/design-system");
|
|
6
|
+
const icons = require("@strapi/icons");
|
|
7
|
+
const admin = require("@strapi/strapi/admin");
|
|
8
|
+
const styledComponents = require("styled-components");
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
12
|
+
if (e) {
|
|
13
|
+
for (const k in e) {
|
|
14
|
+
if (k !== "default") {
|
|
15
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: () => e[k]
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
const React__namespace = /* @__PURE__ */ _interopNamespace(React);
|
|
27
|
+
const PreviewCard = styledComponents.styled(designSystem.Box)`
|
|
28
|
+
border: 1px solid #e4e7eb;
|
|
29
|
+
border-radius: 4px;
|
|
30
|
+
max-width: 400px;
|
|
31
|
+
margin-top: 8px;
|
|
32
|
+
padding: 16px;
|
|
33
|
+
background: white;
|
|
34
|
+
`;
|
|
35
|
+
const UrlPreview = styledComponents.styled.div`
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 8px;
|
|
39
|
+
color: #666;
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
margin-bottom: 8px;
|
|
42
|
+
`;
|
|
43
|
+
const PreviewImage = styledComponents.styled.img`
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: 200px;
|
|
46
|
+
object-fit: cover;
|
|
47
|
+
border-radius: 4px;
|
|
48
|
+
margin-bottom: 12px;
|
|
49
|
+
`;
|
|
50
|
+
const PreviewTitle = styledComponents.styled(designSystem.Typography)`
|
|
51
|
+
font-size: 1.25rem;
|
|
52
|
+
font-weight: bold;
|
|
53
|
+
color: #222;
|
|
54
|
+
margin-bottom: 8px;
|
|
55
|
+
`;
|
|
56
|
+
const PreviewLink = styledComponents.styled.a`
|
|
57
|
+
color: #0099e5;
|
|
58
|
+
text-decoration: underline;
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
font-size: 1rem;
|
|
61
|
+
margin-top: 8px;
|
|
62
|
+
display: inline-block;
|
|
63
|
+
`;
|
|
64
|
+
const UrlFieldInput = React__namespace.forwardRef(
|
|
65
|
+
({ hint, disabled, labelAction, label, name, required, attribute, onChange, error, ...props }, forwardedRef) => {
|
|
66
|
+
const { value } = admin.useField(name);
|
|
67
|
+
const [inputValue, setInputValue] = React.useState(value || "");
|
|
68
|
+
const [metadata, setMetadata] = React.useState(null);
|
|
69
|
+
const [loading, setLoading] = React.useState(false);
|
|
70
|
+
const [fetchError, setFetchError] = React.useState(null);
|
|
71
|
+
const fetchUrlMetadata = async (url) => {
|
|
72
|
+
if (!url || url.trim() === "") {
|
|
73
|
+
setMetadata(null);
|
|
74
|
+
setFetchError(null);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
setLoading(true);
|
|
78
|
+
setFetchError(null);
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch("/url-preview-field/url-metadata", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json"
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({ url })
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Metadata fetch failed: ${response.status} ${response.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
console.log("Metadata received:", JSON.stringify(data));
|
|
92
|
+
setMetadata(data);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("Error fetching URL metadata:", err);
|
|
95
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
96
|
+
setFetchError(`Failed to fetch URL metadata: ${errorMessage}`);
|
|
97
|
+
setMetadata(null);
|
|
98
|
+
} finally {
|
|
99
|
+
setLoading(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
React.useEffect(() => {
|
|
103
|
+
const timeoutId = setTimeout(() => {
|
|
104
|
+
if (inputValue) {
|
|
105
|
+
fetchUrlMetadata(inputValue);
|
|
106
|
+
} else {
|
|
107
|
+
setMetadata(null);
|
|
108
|
+
setFetchError(null);
|
|
109
|
+
}
|
|
110
|
+
}, 1e3);
|
|
111
|
+
return () => clearTimeout(timeoutId);
|
|
112
|
+
}, [inputValue]);
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
setInputValue(value || "");
|
|
115
|
+
}, [value]);
|
|
116
|
+
const handleChange = (e) => {
|
|
117
|
+
const newValue = e.target.value;
|
|
118
|
+
setInputValue(newValue);
|
|
119
|
+
if (onChange) {
|
|
120
|
+
onChange(e);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { name, id: name, error, hint, required, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { action: labelAction, children: label }),
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
126
|
+
designSystem.Field.Input,
|
|
127
|
+
{
|
|
128
|
+
type: "url",
|
|
129
|
+
placeholder: "https://example.com",
|
|
130
|
+
value: inputValue,
|
|
131
|
+
onChange: handleChange,
|
|
132
|
+
disabled
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
loading && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true }),
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Fetching preview..." })
|
|
138
|
+
] }),
|
|
139
|
+
fetchError && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: fetchError }),
|
|
140
|
+
metadata && !loading && !fetchError && /* @__PURE__ */ jsxRuntime.jsx(PreviewCard, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, alignItems: "flex-start", children: [
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsxs(UrlPreview, { children: [
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Globe, { width: "12", height: "12" }),
|
|
143
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: metadata.domain })
|
|
144
|
+
] }),
|
|
145
|
+
metadata.image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
146
|
+
PreviewImage,
|
|
147
|
+
{
|
|
148
|
+
src: metadata.image,
|
|
149
|
+
alt: metadata.title,
|
|
150
|
+
onError: (e) => {
|
|
151
|
+
e.currentTarget.style.display = "none";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
metadata.title && /* @__PURE__ */ jsxRuntime.jsx(PreviewTitle, { as: "div", children: metadata.title }),
|
|
156
|
+
metadata.url && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
157
|
+
PreviewLink,
|
|
158
|
+
{
|
|
159
|
+
href: metadata.url,
|
|
160
|
+
target: "_blank",
|
|
161
|
+
rel: "noopener noreferrer",
|
|
162
|
+
children: [
|
|
163
|
+
"View on ",
|
|
164
|
+
metadata.domain,
|
|
165
|
+
" >"
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
] }) })
|
|
170
|
+
] }) });
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
exports.UrlFieldInput = UrlFieldInput;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Field, Flex, Loader, Typography, Box } from "@strapi/design-system";
|
|
5
|
+
import { Globe } from "@strapi/icons";
|
|
6
|
+
import { useField } from "@strapi/strapi/admin";
|
|
7
|
+
import { styled } from "styled-components";
|
|
8
|
+
const PreviewCard = styled(Box)`
|
|
9
|
+
border: 1px solid #e4e7eb;
|
|
10
|
+
border-radius: 4px;
|
|
11
|
+
max-width: 400px;
|
|
12
|
+
margin-top: 8px;
|
|
13
|
+
padding: 16px;
|
|
14
|
+
background: white;
|
|
15
|
+
`;
|
|
16
|
+
const UrlPreview = styled.div`
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
color: #666;
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
margin-bottom: 8px;
|
|
23
|
+
`;
|
|
24
|
+
const PreviewImage = styled.img`
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 200px;
|
|
27
|
+
object-fit: cover;
|
|
28
|
+
border-radius: 4px;
|
|
29
|
+
margin-bottom: 12px;
|
|
30
|
+
`;
|
|
31
|
+
const PreviewTitle = styled(Typography)`
|
|
32
|
+
font-size: 1.25rem;
|
|
33
|
+
font-weight: bold;
|
|
34
|
+
color: #222;
|
|
35
|
+
margin-bottom: 8px;
|
|
36
|
+
`;
|
|
37
|
+
const PreviewLink = styled.a`
|
|
38
|
+
color: #0099e5;
|
|
39
|
+
text-decoration: underline;
|
|
40
|
+
font-weight: 500;
|
|
41
|
+
font-size: 1rem;
|
|
42
|
+
margin-top: 8px;
|
|
43
|
+
display: inline-block;
|
|
44
|
+
`;
|
|
45
|
+
const UrlFieldInput = React.forwardRef(
|
|
46
|
+
({ hint, disabled, labelAction, label, name, required, attribute, onChange, error, ...props }, forwardedRef) => {
|
|
47
|
+
const { value } = useField(name);
|
|
48
|
+
const [inputValue, setInputValue] = useState(value || "");
|
|
49
|
+
const [metadata, setMetadata] = useState(null);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [fetchError, setFetchError] = useState(null);
|
|
52
|
+
const fetchUrlMetadata = async (url) => {
|
|
53
|
+
if (!url || url.trim() === "") {
|
|
54
|
+
setMetadata(null);
|
|
55
|
+
setFetchError(null);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setLoading(true);
|
|
59
|
+
setFetchError(null);
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch("/url-preview-field/url-metadata", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: {
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify({ url })
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Metadata fetch failed: ${response.status} ${response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
console.log("Metadata received:", JSON.stringify(data));
|
|
73
|
+
setMetadata(data);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("Error fetching URL metadata:", err);
|
|
76
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
77
|
+
setFetchError(`Failed to fetch URL metadata: ${errorMessage}`);
|
|
78
|
+
setMetadata(null);
|
|
79
|
+
} finally {
|
|
80
|
+
setLoading(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const timeoutId = setTimeout(() => {
|
|
85
|
+
if (inputValue) {
|
|
86
|
+
fetchUrlMetadata(inputValue);
|
|
87
|
+
} else {
|
|
88
|
+
setMetadata(null);
|
|
89
|
+
setFetchError(null);
|
|
90
|
+
}
|
|
91
|
+
}, 1e3);
|
|
92
|
+
return () => clearTimeout(timeoutId);
|
|
93
|
+
}, [inputValue]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setInputValue(value || "");
|
|
96
|
+
}, [value]);
|
|
97
|
+
const handleChange = (e) => {
|
|
98
|
+
const newValue = e.target.value;
|
|
99
|
+
setInputValue(newValue);
|
|
100
|
+
if (onChange) {
|
|
101
|
+
onChange(e);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ jsx(Field.Root, { name, id: name, error, hint, required, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
105
|
+
/* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: label }),
|
|
106
|
+
/* @__PURE__ */ jsx(
|
|
107
|
+
Field.Input,
|
|
108
|
+
{
|
|
109
|
+
type: "url",
|
|
110
|
+
placeholder: "https://example.com",
|
|
111
|
+
value: inputValue,
|
|
112
|
+
onChange: handleChange,
|
|
113
|
+
disabled
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
loading && /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
|
|
117
|
+
/* @__PURE__ */ jsx(Loader, { small: true }),
|
|
118
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Fetching preview..." })
|
|
119
|
+
] }),
|
|
120
|
+
fetchError && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: fetchError }),
|
|
121
|
+
metadata && !loading && !fetchError && /* @__PURE__ */ jsx(PreviewCard, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, alignItems: "flex-start", children: [
|
|
122
|
+
/* @__PURE__ */ jsxs(UrlPreview, { children: [
|
|
123
|
+
/* @__PURE__ */ jsx(Globe, { width: "12", height: "12" }),
|
|
124
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", children: metadata.domain })
|
|
125
|
+
] }),
|
|
126
|
+
metadata.image && /* @__PURE__ */ jsx(
|
|
127
|
+
PreviewImage,
|
|
128
|
+
{
|
|
129
|
+
src: metadata.image,
|
|
130
|
+
alt: metadata.title,
|
|
131
|
+
onError: (e) => {
|
|
132
|
+
e.currentTarget.style.display = "none";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
metadata.title && /* @__PURE__ */ jsx(PreviewTitle, { as: "div", children: metadata.title }),
|
|
137
|
+
metadata.url && /* @__PURE__ */ jsxs(
|
|
138
|
+
PreviewLink,
|
|
139
|
+
{
|
|
140
|
+
href: metadata.url,
|
|
141
|
+
target: "_blank",
|
|
142
|
+
rel: "noopener noreferrer",
|
|
143
|
+
children: [
|
|
144
|
+
"View on ",
|
|
145
|
+
metadata.domain,
|
|
146
|
+
" >"
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] }) })
|
|
151
|
+
] }) });
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
export {
|
|
155
|
+
UrlFieldInput
|
|
156
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const React = require("react");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const designSystem = require("@strapi/design-system");
|
|
5
|
+
const icons = require("@strapi/icons");
|
|
6
|
+
const styledComponents = require("styled-components");
|
|
7
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
8
|
+
const v = glob[path];
|
|
9
|
+
if (v) {
|
|
10
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
11
|
+
}
|
|
12
|
+
return new Promise((_, reject) => {
|
|
13
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
14
|
+
reject.bind(
|
|
15
|
+
null,
|
|
16
|
+
new Error(
|
|
17
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const PLUGIN_ID = "url-preview-field";
|
|
24
|
+
const Initializer = ({ setPlugin }) => {
|
|
25
|
+
const ref = React.useRef(setPlugin);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
ref.current(PLUGIN_ID);
|
|
28
|
+
}, []);
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
const IconBox = styledComponents.styled(designSystem.Flex)`
|
|
32
|
+
/* Hard code color values */
|
|
33
|
+
/* to stay consistent between themes */
|
|
34
|
+
background-color: #f0f0ff; /* primary100 */
|
|
35
|
+
border: 1px solid #d9d8ff; /* primary200 */
|
|
36
|
+
|
|
37
|
+
svg > path {
|
|
38
|
+
fill: #4945ff; /* primary600 */
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
const UrlFieldIcon = () => {
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsx(IconBox, { justifyContent: "center", alignItems: "center", width: 7, height: 6, hasRadius: true, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, {}) });
|
|
43
|
+
};
|
|
44
|
+
const index = {
|
|
45
|
+
register(app) {
|
|
46
|
+
app.registerPlugin({
|
|
47
|
+
id: PLUGIN_ID,
|
|
48
|
+
initializer: Initializer,
|
|
49
|
+
isReady: false,
|
|
50
|
+
name: PLUGIN_ID
|
|
51
|
+
});
|
|
52
|
+
app.customFields.register({
|
|
53
|
+
name: "url",
|
|
54
|
+
pluginId: PLUGIN_ID,
|
|
55
|
+
// Use the PLUGIN_ID constant to match server registration
|
|
56
|
+
type: "string",
|
|
57
|
+
intlLabel: {
|
|
58
|
+
id: "url-preview-field.url.label",
|
|
59
|
+
defaultMessage: "URL"
|
|
60
|
+
},
|
|
61
|
+
intlDescription: {
|
|
62
|
+
id: "url-preview-field.url.description",
|
|
63
|
+
defaultMessage: "Enter an URL to see a social share card preview."
|
|
64
|
+
},
|
|
65
|
+
icon: UrlFieldIcon,
|
|
66
|
+
components: {
|
|
67
|
+
Input: async () => Promise.resolve().then(() => require("../_chunks/UrlFieldInput-BhujW-Fo.js")).then((module2) => ({
|
|
68
|
+
default: module2.UrlFieldInput
|
|
69
|
+
}))
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
async registerTrads({ locales }) {
|
|
74
|
+
return Promise.all(
|
|
75
|
+
locales.map(async (locale) => {
|
|
76
|
+
try {
|
|
77
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-B4KWt_jN.js")) }), `./translations/${locale}.json`, 3);
|
|
78
|
+
return { data, locale };
|
|
79
|
+
} catch {
|
|
80
|
+
return { data: {}, locale };
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
module.exports = index;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Flex } from "@strapi/design-system";
|
|
4
|
+
import { ExternalLink } from "@strapi/icons";
|
|
5
|
+
import { styled } from "styled-components";
|
|
6
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
7
|
+
const v = glob[path];
|
|
8
|
+
if (v) {
|
|
9
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
10
|
+
}
|
|
11
|
+
return new Promise((_, reject) => {
|
|
12
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
13
|
+
reject.bind(
|
|
14
|
+
null,
|
|
15
|
+
new Error(
|
|
16
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const PLUGIN_ID = "url-preview-field";
|
|
23
|
+
const Initializer = ({ setPlugin }) => {
|
|
24
|
+
const ref = useRef(setPlugin);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
ref.current(PLUGIN_ID);
|
|
27
|
+
}, []);
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
const IconBox = styled(Flex)`
|
|
31
|
+
/* Hard code color values */
|
|
32
|
+
/* to stay consistent between themes */
|
|
33
|
+
background-color: #f0f0ff; /* primary100 */
|
|
34
|
+
border: 1px solid #d9d8ff; /* primary200 */
|
|
35
|
+
|
|
36
|
+
svg > path {
|
|
37
|
+
fill: #4945ff; /* primary600 */
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
const UrlFieldIcon = () => {
|
|
41
|
+
return /* @__PURE__ */ jsx(IconBox, { justifyContent: "center", alignItems: "center", width: 7, height: 6, hasRadius: true, "aria-hidden": true, children: /* @__PURE__ */ jsx(ExternalLink, {}) });
|
|
42
|
+
};
|
|
43
|
+
const index = {
|
|
44
|
+
register(app) {
|
|
45
|
+
app.registerPlugin({
|
|
46
|
+
id: PLUGIN_ID,
|
|
47
|
+
initializer: Initializer,
|
|
48
|
+
isReady: false,
|
|
49
|
+
name: PLUGIN_ID
|
|
50
|
+
});
|
|
51
|
+
app.customFields.register({
|
|
52
|
+
name: "url",
|
|
53
|
+
pluginId: PLUGIN_ID,
|
|
54
|
+
// Use the PLUGIN_ID constant to match server registration
|
|
55
|
+
type: "string",
|
|
56
|
+
intlLabel: {
|
|
57
|
+
id: "url-preview-field.url.label",
|
|
58
|
+
defaultMessage: "URL"
|
|
59
|
+
},
|
|
60
|
+
intlDescription: {
|
|
61
|
+
id: "url-preview-field.url.description",
|
|
62
|
+
defaultMessage: "Enter an URL to see a social share card preview."
|
|
63
|
+
},
|
|
64
|
+
icon: UrlFieldIcon,
|
|
65
|
+
components: {
|
|
66
|
+
Input: async () => import("../_chunks/UrlFieldInput-D0OIBuvW.mjs").then((module) => ({
|
|
67
|
+
default: module.UrlFieldInput
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
async registerTrads({ locales }) {
|
|
73
|
+
return Promise.all(
|
|
74
|
+
locales.map(async (locale) => {
|
|
75
|
+
try {
|
|
76
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("../_chunks/en-Byx4XI2L.mjs") }), `./translations/${locale}.json`, 3);
|
|
77
|
+
return { data, locale };
|
|
78
|
+
} catch {
|
|
79
|
+
return { data: {}, locale };
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export {
|
|
86
|
+
index as default
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const UrlFieldIcon: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type InputProps } from '@strapi/strapi/admin';
|
|
3
|
+
type UrlFieldInputProps = InputProps & {
|
|
4
|
+
labelAction?: React.ReactNode;
|
|
5
|
+
attribute?: {
|
|
6
|
+
options?: {
|
|
7
|
+
url?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const UrlFieldInput: React.ForwardRefExoticComponent<UrlFieldInputProps & React.RefAttributes<HTMLButtonElement>>;
|
|
14
|
+
export { UrlFieldInput };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_ID = "url-preview-field";
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fetch = require("node-fetch");
|
|
3
|
+
const cheerio = require("cheerio");
|
|
4
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
5
|
+
function _interopNamespace(e) {
|
|
6
|
+
if (e && e.__esModule) return e;
|
|
7
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
8
|
+
if (e) {
|
|
9
|
+
for (const k in e) {
|
|
10
|
+
if (k !== "default") {
|
|
11
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: () => e[k]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
const fetch__default = /* @__PURE__ */ _interopDefault(fetch);
|
|
23
|
+
const cheerio__namespace = /* @__PURE__ */ _interopNamespace(cheerio);
|
|
24
|
+
const bootstrap = ({ strapi }) => {
|
|
25
|
+
};
|
|
26
|
+
const destroy = ({ strapi }) => {
|
|
27
|
+
};
|
|
28
|
+
const PLUGIN_ID = "url-preview-field";
|
|
29
|
+
const register = ({ strapi }) => {
|
|
30
|
+
strapi.customFields.register({
|
|
31
|
+
name: "url",
|
|
32
|
+
plugin: PLUGIN_ID,
|
|
33
|
+
type: "string",
|
|
34
|
+
inputSize: {
|
|
35
|
+
default: 4,
|
|
36
|
+
isResizable: true
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const config = {
|
|
41
|
+
default: {},
|
|
42
|
+
validator() {
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const contentTypes = {};
|
|
46
|
+
const controller = ({ strapi }) => ({
|
|
47
|
+
index(ctx) {
|
|
48
|
+
ctx.body = strapi.plugin("url-preview-field").service("service").getWelcomeMessage();
|
|
49
|
+
},
|
|
50
|
+
async fetchMetadata(ctx) {
|
|
51
|
+
const { url } = ctx.request.body;
|
|
52
|
+
ctx.body = await strapi.plugin("url-preview-field").service("service").getUrlMetadata(url);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const controllers = {
|
|
56
|
+
controller
|
|
57
|
+
};
|
|
58
|
+
const middlewares = {};
|
|
59
|
+
const policies = {};
|
|
60
|
+
const routes = [
|
|
61
|
+
{
|
|
62
|
+
method: "GET",
|
|
63
|
+
path: "/",
|
|
64
|
+
// name of the controller file & the method.
|
|
65
|
+
handler: "controller.index",
|
|
66
|
+
config: {
|
|
67
|
+
policies: []
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: "/url-metadata",
|
|
73
|
+
handler: "controller.fetchMetadata",
|
|
74
|
+
config: {
|
|
75
|
+
auth: false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
const service = ({ strapi }) => ({
|
|
80
|
+
getWelcomeMessage() {
|
|
81
|
+
return "Welcome to Strapi 🚀";
|
|
82
|
+
},
|
|
83
|
+
async getUrlMetadata(url) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch__default.default(url);
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Failed to fetch URL: ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
const html = await response.text();
|
|
90
|
+
const $ = cheerio__namespace.load(html);
|
|
91
|
+
const title = $('meta[property="og:title"]').attr("content") || $("title").text() || "";
|
|
92
|
+
const image = $('meta[property="og:image"]').attr("content") || $('meta[name="twitter:image"]').attr("content") || "";
|
|
93
|
+
const domain = (() => {
|
|
94
|
+
try {
|
|
95
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
96
|
+
} catch {
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
console.log(
|
|
101
|
+
`Found Data: ${JSON.stringify({
|
|
102
|
+
title,
|
|
103
|
+
image,
|
|
104
|
+
url,
|
|
105
|
+
domain
|
|
106
|
+
})}`
|
|
107
|
+
);
|
|
108
|
+
return {
|
|
109
|
+
title,
|
|
110
|
+
image,
|
|
111
|
+
url,
|
|
112
|
+
domain
|
|
113
|
+
};
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error("Error fetching URL metadata:", err);
|
|
116
|
+
return {
|
|
117
|
+
title: "",
|
|
118
|
+
image: "",
|
|
119
|
+
url,
|
|
120
|
+
domain: "",
|
|
121
|
+
error: err.message
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const services = {
|
|
127
|
+
service
|
|
128
|
+
};
|
|
129
|
+
const index = {
|
|
130
|
+
register,
|
|
131
|
+
bootstrap,
|
|
132
|
+
destroy,
|
|
133
|
+
config,
|
|
134
|
+
controllers,
|
|
135
|
+
routes,
|
|
136
|
+
services,
|
|
137
|
+
contentTypes,
|
|
138
|
+
policies,
|
|
139
|
+
middlewares
|
|
140
|
+
};
|
|
141
|
+
module.exports = index;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import * as cheerio from "cheerio";
|
|
3
|
+
const bootstrap = ({ strapi }) => {
|
|
4
|
+
};
|
|
5
|
+
const destroy = ({ strapi }) => {
|
|
6
|
+
};
|
|
7
|
+
const PLUGIN_ID = "url-preview-field";
|
|
8
|
+
const register = ({ strapi }) => {
|
|
9
|
+
strapi.customFields.register({
|
|
10
|
+
name: "url",
|
|
11
|
+
plugin: PLUGIN_ID,
|
|
12
|
+
type: "string",
|
|
13
|
+
inputSize: {
|
|
14
|
+
default: 4,
|
|
15
|
+
isResizable: true
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const config = {
|
|
20
|
+
default: {},
|
|
21
|
+
validator() {
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const contentTypes = {};
|
|
25
|
+
const controller = ({ strapi }) => ({
|
|
26
|
+
index(ctx) {
|
|
27
|
+
ctx.body = strapi.plugin("url-preview-field").service("service").getWelcomeMessage();
|
|
28
|
+
},
|
|
29
|
+
async fetchMetadata(ctx) {
|
|
30
|
+
const { url } = ctx.request.body;
|
|
31
|
+
ctx.body = await strapi.plugin("url-preview-field").service("service").getUrlMetadata(url);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const controllers = {
|
|
35
|
+
controller
|
|
36
|
+
};
|
|
37
|
+
const middlewares = {};
|
|
38
|
+
const policies = {};
|
|
39
|
+
const routes = [
|
|
40
|
+
{
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: "/",
|
|
43
|
+
// name of the controller file & the method.
|
|
44
|
+
handler: "controller.index",
|
|
45
|
+
config: {
|
|
46
|
+
policies: []
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
method: "POST",
|
|
51
|
+
path: "/url-metadata",
|
|
52
|
+
handler: "controller.fetchMetadata",
|
|
53
|
+
config: {
|
|
54
|
+
auth: false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
const service = ({ strapi }) => ({
|
|
59
|
+
getWelcomeMessage() {
|
|
60
|
+
return "Welcome to Strapi 🚀";
|
|
61
|
+
},
|
|
62
|
+
async getUrlMetadata(url) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(url);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`Failed to fetch URL: ${response.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
const html = await response.text();
|
|
69
|
+
const $ = cheerio.load(html);
|
|
70
|
+
const title = $('meta[property="og:title"]').attr("content") || $("title").text() || "";
|
|
71
|
+
const image = $('meta[property="og:image"]').attr("content") || $('meta[name="twitter:image"]').attr("content") || "";
|
|
72
|
+
const domain = (() => {
|
|
73
|
+
try {
|
|
74
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
75
|
+
} catch {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
console.log(
|
|
80
|
+
`Found Data: ${JSON.stringify({
|
|
81
|
+
title,
|
|
82
|
+
image,
|
|
83
|
+
url,
|
|
84
|
+
domain
|
|
85
|
+
})}`
|
|
86
|
+
);
|
|
87
|
+
return {
|
|
88
|
+
title,
|
|
89
|
+
image,
|
|
90
|
+
url,
|
|
91
|
+
domain
|
|
92
|
+
};
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("Error fetching URL metadata:", err);
|
|
95
|
+
return {
|
|
96
|
+
title: "",
|
|
97
|
+
image: "",
|
|
98
|
+
url,
|
|
99
|
+
domain: "",
|
|
100
|
+
error: err.message
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const services = {
|
|
106
|
+
service
|
|
107
|
+
};
|
|
108
|
+
const index = {
|
|
109
|
+
register,
|
|
110
|
+
bootstrap,
|
|
111
|
+
destroy,
|
|
112
|
+
config,
|
|
113
|
+
controllers,
|
|
114
|
+
routes,
|
|
115
|
+
services,
|
|
116
|
+
contentTypes,
|
|
117
|
+
policies,
|
|
118
|
+
middlewares
|
|
119
|
+
};
|
|
120
|
+
export {
|
|
121
|
+
index as default
|
|
122
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
register: ({ strapi }: {
|
|
3
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
4
|
+
}) => void;
|
|
5
|
+
bootstrap: ({ strapi }: {
|
|
6
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
7
|
+
}) => void;
|
|
8
|
+
destroy: ({ strapi }: {
|
|
9
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
10
|
+
}) => void;
|
|
11
|
+
config: {
|
|
12
|
+
default: {};
|
|
13
|
+
validator(): void;
|
|
14
|
+
};
|
|
15
|
+
controllers: {
|
|
16
|
+
controller: ({ strapi }: {
|
|
17
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
18
|
+
}) => {
|
|
19
|
+
index(ctx: any): void;
|
|
20
|
+
fetchMetadata(ctx: any): Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
routes: ({
|
|
24
|
+
method: string;
|
|
25
|
+
path: string;
|
|
26
|
+
handler: string;
|
|
27
|
+
/**
|
|
28
|
+
* Plugin server methods
|
|
29
|
+
*/
|
|
30
|
+
config: {
|
|
31
|
+
policies: any[];
|
|
32
|
+
auth?: undefined;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
method: string;
|
|
36
|
+
path: string;
|
|
37
|
+
handler: string;
|
|
38
|
+
config: {
|
|
39
|
+
auth: boolean;
|
|
40
|
+
policies?: undefined;
|
|
41
|
+
};
|
|
42
|
+
})[];
|
|
43
|
+
services: {
|
|
44
|
+
service: ({ strapi }: {
|
|
45
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
46
|
+
}) => {
|
|
47
|
+
getWelcomeMessage(): string;
|
|
48
|
+
getUrlMetadata(url: string): Promise<{
|
|
49
|
+
title: string;
|
|
50
|
+
image: string;
|
|
51
|
+
url: string;
|
|
52
|
+
domain: string;
|
|
53
|
+
error?: undefined;
|
|
54
|
+
} | {
|
|
55
|
+
title: string;
|
|
56
|
+
image: string;
|
|
57
|
+
url: string;
|
|
58
|
+
domain: string;
|
|
59
|
+
error: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
contentTypes: {};
|
|
64
|
+
policies: {};
|
|
65
|
+
middlewares: {};
|
|
66
|
+
};
|
|
67
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_ID = "url-preview-field";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare const _default: ({
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
handler: string;
|
|
5
|
+
config: {
|
|
6
|
+
policies: any[];
|
|
7
|
+
auth?: undefined;
|
|
8
|
+
};
|
|
9
|
+
} | {
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
handler: string;
|
|
13
|
+
config: {
|
|
14
|
+
auth: boolean;
|
|
15
|
+
policies?: undefined;
|
|
16
|
+
};
|
|
17
|
+
})[];
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
service: ({ strapi }: {
|
|
3
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
4
|
+
}) => {
|
|
5
|
+
getWelcomeMessage(): string;
|
|
6
|
+
getUrlMetadata(url: string): Promise<{
|
|
7
|
+
title: string;
|
|
8
|
+
image: string;
|
|
9
|
+
url: string;
|
|
10
|
+
domain: string;
|
|
11
|
+
error?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
title: string;
|
|
14
|
+
image: string;
|
|
15
|
+
url: string;
|
|
16
|
+
domain: string;
|
|
17
|
+
error: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Core } from '@strapi/strapi';
|
|
2
|
+
declare const service: ({ strapi }: {
|
|
3
|
+
strapi: Core.Strapi;
|
|
4
|
+
}) => {
|
|
5
|
+
getWelcomeMessage(): string;
|
|
6
|
+
getUrlMetadata(url: string): Promise<{
|
|
7
|
+
title: string;
|
|
8
|
+
image: string;
|
|
9
|
+
url: string;
|
|
10
|
+
domain: string;
|
|
11
|
+
error?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
title: string;
|
|
14
|
+
image: string;
|
|
15
|
+
url: string;
|
|
16
|
+
domain: string;
|
|
17
|
+
error: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export default service;
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"keywords": [],
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/uptech/strapi-url-preview-field.git"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
"./package.json": "./package.json",
|
|
11
|
+
"./strapi-admin": {
|
|
12
|
+
"types": "./dist/admin/src/index.d.ts",
|
|
13
|
+
"source": "./admin/src/index.ts",
|
|
14
|
+
"import": "./dist/admin/index.mjs",
|
|
15
|
+
"require": "./dist/admin/index.js",
|
|
16
|
+
"default": "./dist/admin/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./strapi-server": {
|
|
19
|
+
"types": "./dist/server/src/index.d.ts",
|
|
20
|
+
"source": "./server/src/index.ts",
|
|
21
|
+
"import": "./dist/server/index.mjs",
|
|
22
|
+
"require": "./dist/server/index.js",
|
|
23
|
+
"default": "./dist/server/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "strapi-plugin build",
|
|
31
|
+
"watch": "strapi-plugin watch",
|
|
32
|
+
"watch:link": "strapi-plugin watch:link",
|
|
33
|
+
"verify": "strapi-plugin verify",
|
|
34
|
+
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
|
35
|
+
"test:ts:back": "run -T tsc -p server/tsconfig.json"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@strapi/design-system": "^2.0.0-rc.29",
|
|
39
|
+
"@strapi/icons": "^2.0.0-rc.29",
|
|
40
|
+
"axios": "^1.6.0",
|
|
41
|
+
"cheerio": "^1.1.2",
|
|
42
|
+
"node-fetch": "^2.7.0",
|
|
43
|
+
"react-intl": "^7.1.11"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@strapi/sdk-plugin": "^5.3.2",
|
|
47
|
+
"@strapi/strapi": "^5.18.0",
|
|
48
|
+
"@strapi/typescript-utils": "^5.18.0",
|
|
49
|
+
"@types/cheerio": "^0.22.35",
|
|
50
|
+
"@types/node-fetch": "^2.6.12",
|
|
51
|
+
"@types/react": "^19.1.8",
|
|
52
|
+
"@types/react-dom": "^19.1.6",
|
|
53
|
+
"prettier": "^3.6.2",
|
|
54
|
+
"react": "^18.3.1",
|
|
55
|
+
"react-dom": "^18.3.1",
|
|
56
|
+
"react-router-dom": "^6.30.1",
|
|
57
|
+
"styled-components": "^6.1.19",
|
|
58
|
+
"typescript": "^5.8.3"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@strapi/sdk-plugin": "^5.3.2",
|
|
62
|
+
"@strapi/strapi": "^5.18.0",
|
|
63
|
+
"react": "^18.3.1",
|
|
64
|
+
"react-dom": "^18.3.1",
|
|
65
|
+
"react-router-dom": "^6.30.1",
|
|
66
|
+
"styled-components": "^6.1.19"
|
|
67
|
+
},
|
|
68
|
+
"strapi": {
|
|
69
|
+
"kind": "plugin",
|
|
70
|
+
"name": "url-preview-field",
|
|
71
|
+
"displayName": "URL Share Preview",
|
|
72
|
+
"description": "A custom field plugin for Strapi that allows you to preview the URL. The preview is similar to the one you see when you share a link on social media."
|
|
73
|
+
},
|
|
74
|
+
"name": "@uptechworks/url-preview-field",
|
|
75
|
+
"description": "A custom field plugin for Strapi that allows you to preview the URL. The preview is similar to the one you see when you share a link on social media.",
|
|
76
|
+
"license": "MIT",
|
|
77
|
+
"author": "Uptech Studio <jon@uptechstudio.com>",
|
|
78
|
+
"main": "index.js",
|
|
79
|
+
"bugs": {
|
|
80
|
+
"url": "https://github.com/uptech/strapi-url-preview-field/issues"
|
|
81
|
+
},
|
|
82
|
+
"homepage": "https://github.com/uptech/strapi-url-preview-field#readme"
|
|
83
|
+
}
|