strapi-plugin-blogseo 1.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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/admin/Settings-BJbpMBlx.mjs +149 -0
- package/dist/admin/Settings-C3-8r0LW.js +149 -0
- package/dist/admin/en-CEwYiwKv.mjs +8 -0
- package/dist/admin/en-DXs_qePG.js +8 -0
- package/dist/admin/index.js +68 -0
- package/dist/admin/index.mjs +68 -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/index.d.ts +3 -0
- package/dist/admin/src/pages/Settings.d.ts +2 -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 +206 -0
- package/dist/server/index.mjs +206 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +7 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/blogseo.d.ts +11 -0
- package/dist/server/src/controllers/index.d.ts +12 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +98 -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/index.d.ts +12 -0
- package/dist/server/src/routes/content-api/index.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +25 -0
- package/dist/server/src/services/blogseo.d.ts +43 -0
- package/dist/server/src/services/index.d.ts +43 -0
- package/dist/server/src/utils/getService.d.ts +3 -0
- package/package.json +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vincent JOSSE (BlogSEO)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# BlogSEO for Strapi
|
|
2
|
+
|
|
3
|
+
Connect your Strapi project to [BlogSEO](https://www.blogseo.io) — the AI-powered SEO platform that writes, illustrates and publishes SEO-optimized blog articles to your site on autopilot, and builds high-quality backlinks to grow your organic traffic.
|
|
4
|
+
|
|
5
|
+
This plugin links your Strapi instance to your BlogSEO account in a couple of clicks: no manual API-token creation, no copy-pasting content-type IDs between dashboards.
|
|
6
|
+
|
|
7
|
+
## What is BlogSEO?
|
|
8
|
+
|
|
9
|
+
[BlogSEO](https://www.blogseo.io) helps businesses grow their organic search traffic end to end:
|
|
10
|
+
|
|
11
|
+
- **AI-written SEO articles** — long-form, SEO-optimized articles researched and written for your niche, in 100+ languages, with AI-generated cover and inline images.
|
|
12
|
+
- **High-quality backlinks** — grow your Domain Rating with backlink building, and track your backlink profile over time.
|
|
13
|
+
- **Publishing on autopilot** — articles are scheduled and published straight into your CMS (Strapi, WordPress, Shopify, Ghost, and many more), formatted for your content model.
|
|
14
|
+
- **SEO analytics** — keyword tracking and Google Search Console insights to measure what your content brings in.
|
|
15
|
+
|
|
16
|
+
The plugin is free and open source. Publishing requires a [BlogSEO](https://www.blogseo.io) account.
|
|
17
|
+
|
|
18
|
+
## What the plugin does
|
|
19
|
+
|
|
20
|
+
- Adds a **BlogSEO** page under **Settings** in your Strapi admin panel.
|
|
21
|
+
- Lets you connect with a single **connection key** pasted from your BlogSEO dashboard.
|
|
22
|
+
- Automatically creates the **Strapi API token** BlogSEO publishes with — you never have to create or handle one manually, and disconnecting revokes it.
|
|
23
|
+
- Detects your **content types and locales** and registers everything with BlogSEO in one click.
|
|
24
|
+
- Shows the **connection status** and lets you disconnect (revoking the token) at any time.
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Strapi **v5**
|
|
29
|
+
- A [BlogSEO account](https://www.blogseo.io)
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install strapi-plugin-blogseo
|
|
35
|
+
# or
|
|
36
|
+
yarn add strapi-plugin-blogseo
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then rebuild your admin panel:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run build
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The plugin is enabled automatically once installed. No configuration file changes are required.
|
|
46
|
+
|
|
47
|
+
## Getting started
|
|
48
|
+
|
|
49
|
+
1. In your [BlogSEO dashboard](https://app.blogseo.io), go to **Integrations → Strapi** and choose **Connect via plugin** to generate a connection key.
|
|
50
|
+
2. In your Strapi admin panel, go to **Settings → BlogSEO**, paste the key, pick the collection your articles should be published into, and click **Connect**.
|
|
51
|
+
3. That's it — BlogSEO now publishes your scheduled articles straight into that collection.
|
|
52
|
+
|
|
53
|
+
Full walkthrough: [BlogSEO Strapi integration docs](https://www.blogseo.io/docs/integrations/strapi).
|
|
54
|
+
|
|
55
|
+
## Advanced configuration
|
|
56
|
+
|
|
57
|
+
All options are optional and go in `config/plugins.ts`:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
export default {
|
|
61
|
+
"blogseo": {
|
|
62
|
+
enabled: true,
|
|
63
|
+
config: {
|
|
64
|
+
// Override the BlogSEO API endpoint (self-hosted / staging setups)
|
|
65
|
+
blogSeoApiUrl: "https://app.blogseo.io",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Support
|
|
72
|
+
|
|
73
|
+
- Website: [https://www.blogseo.io](https://www.blogseo.io)
|
|
74
|
+
- Documentation: [https://www.blogseo.io/docs/integrations/strapi](https://www.blogseo.io/docs/integrations/strapi)
|
|
75
|
+
- Email: [support@blogseo.io](mailto:support@blogseo.io)
|
|
76
|
+
|
|
77
|
+
Found a bug or have a feature request? [Open an issue](https://github.com/baballev/blogseo-strapi-plugin/issues).
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Main, Box, Flex, Typography, Button, Field, SingleSelect, SingleSelectOption } from "@strapi/design-system";
|
|
4
|
+
import { useFetchClient, useNotification, Page, Layouts } from "@strapi/strapi/admin";
|
|
5
|
+
const PLUGIN_BASE = "/blogseo";
|
|
6
|
+
const extractErrorMessage = (error, fallback) => {
|
|
7
|
+
const data = error?.response?.data;
|
|
8
|
+
return data?.error?.message ?? fallback;
|
|
9
|
+
};
|
|
10
|
+
const Settings = () => {
|
|
11
|
+
const { get, post } = useFetchClient();
|
|
12
|
+
const { toggleNotification } = useNotification();
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
15
|
+
const [status, setStatus] = useState(null);
|
|
16
|
+
const [contentTypes, setContentTypes] = useState([]);
|
|
17
|
+
const [apiKey, setApiKey] = useState("");
|
|
18
|
+
const [pluralApiId, setPluralApiId] = useState("");
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const load = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const [statusRes, contentTypesRes] = await Promise.all([
|
|
23
|
+
get(`${PLUGIN_BASE}/status`),
|
|
24
|
+
get(`${PLUGIN_BASE}/content-types`)
|
|
25
|
+
]);
|
|
26
|
+
setStatus(statusRes.data);
|
|
27
|
+
setContentTypes(contentTypesRes.data.contentTypes ?? []);
|
|
28
|
+
if (contentTypesRes.data.contentTypes?.length)
|
|
29
|
+
setPluralApiId(contentTypesRes.data.contentTypes[0].pluralApiId);
|
|
30
|
+
} catch {
|
|
31
|
+
toggleNotification({ type: "danger", message: "Could not load BlogSEO settings." });
|
|
32
|
+
} finally {
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
load();
|
|
37
|
+
}, [get, toggleNotification]);
|
|
38
|
+
const handleConnect = async () => {
|
|
39
|
+
if (!apiKey.trim())
|
|
40
|
+
return toggleNotification({ type: "warning", message: "Paste your BlogSEO connection key first." });
|
|
41
|
+
if (!pluralApiId)
|
|
42
|
+
return toggleNotification({ type: "warning", message: "Pick a collection to publish into." });
|
|
43
|
+
setIsSubmitting(true);
|
|
44
|
+
try {
|
|
45
|
+
const { data } = await post(
|
|
46
|
+
`${PLUGIN_BASE}/connect`,
|
|
47
|
+
{ apiKey: apiKey.trim(), pluralApiId }
|
|
48
|
+
);
|
|
49
|
+
setStatus({ connected: true, siteUrl: data.siteUrl, pluralApiId });
|
|
50
|
+
setApiKey("");
|
|
51
|
+
toggleNotification({
|
|
52
|
+
type: "success",
|
|
53
|
+
message: "Connected to BlogSEO! Articles will now publish into this Strapi."
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
toggleNotification({
|
|
57
|
+
type: "danger",
|
|
58
|
+
message: extractErrorMessage(error, "Failed to connect to BlogSEO.")
|
|
59
|
+
});
|
|
60
|
+
} finally {
|
|
61
|
+
setIsSubmitting(false);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const handleDisconnect = async () => {
|
|
65
|
+
setIsSubmitting(true);
|
|
66
|
+
try {
|
|
67
|
+
await post(`${PLUGIN_BASE}/disconnect`, {});
|
|
68
|
+
setStatus({ connected: false, siteUrl: null, pluralApiId: null });
|
|
69
|
+
toggleNotification({ type: "success", message: "Disconnected from BlogSEO." });
|
|
70
|
+
} catch {
|
|
71
|
+
toggleNotification({ type: "danger", message: "Failed to disconnect." });
|
|
72
|
+
} finally {
|
|
73
|
+
setIsSubmitting(false);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (isLoading) return /* @__PURE__ */ jsx(Page.Loading, {});
|
|
77
|
+
return /* @__PURE__ */ jsxs(Main, { children: [
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
79
|
+
Layouts.Header,
|
|
80
|
+
{
|
|
81
|
+
title: "BlogSEO",
|
|
82
|
+
subtitle: "Publish AI-generated SEO articles and build backlinks straight into this Strapi."
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: status?.connected ? /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
|
|
86
|
+
/* @__PURE__ */ jsx(Typography, { variant: "delta", children: "Connected to BlogSEO" }),
|
|
87
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
88
|
+
/* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "Publishing into " }),
|
|
89
|
+
/* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: status.pluralApiId ?? "—" }),
|
|
90
|
+
status.siteUrl ? /* @__PURE__ */ jsxs(Typography, { textColor: "neutral600", children: [
|
|
91
|
+
" · ",
|
|
92
|
+
status.siteUrl
|
|
93
|
+
] }) : null
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Button, { variant: "danger-light", onClick: handleDisconnect, loading: isSubmitting, children: "Disconnect" }) })
|
|
96
|
+
] }) : /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
|
|
97
|
+
/* @__PURE__ */ jsx(Typography, { variant: "delta", children: "Connect your BlogSEO account" }),
|
|
98
|
+
/* @__PURE__ */ jsxs(
|
|
99
|
+
Field.Root,
|
|
100
|
+
{
|
|
101
|
+
name: "apiKey",
|
|
102
|
+
hint: "Generate this in your BlogSEO dashboard → Integrations → Strapi → “Connect with the Strapi plugin”.",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "BlogSEO connection key" }),
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
Field.Input,
|
|
107
|
+
{
|
|
108
|
+
type: "password",
|
|
109
|
+
value: apiKey,
|
|
110
|
+
onChange: (event) => setApiKey(event.target.value),
|
|
111
|
+
placeholder: "blogseo_sp_..."
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
/* @__PURE__ */ jsx(Field.Hint, {})
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ jsxs(
|
|
119
|
+
Field.Root,
|
|
120
|
+
{
|
|
121
|
+
name: "contentType",
|
|
122
|
+
hint: "New articles from BlogSEO are created as entries here.",
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Collection to publish into" }),
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
SingleSelect,
|
|
127
|
+
{
|
|
128
|
+
value: pluralApiId,
|
|
129
|
+
onChange: (value) => setPluralApiId(String(value)),
|
|
130
|
+
placeholder: "Select a collection",
|
|
131
|
+
children: contentTypes.map((contentType) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: contentType.pluralApiId, children: [
|
|
132
|
+
contentType.displayName,
|
|
133
|
+
" (",
|
|
134
|
+
contentType.pluralApiId,
|
|
135
|
+
")"
|
|
136
|
+
] }, contentType.uid))
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
/* @__PURE__ */ jsx(Field.Hint, {})
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Button, { onClick: handleConnect, loading: isSubmitting, disabled: !apiKey || !pluralApiId, children: "Connect" }) })
|
|
144
|
+
] }) }) })
|
|
145
|
+
] });
|
|
146
|
+
};
|
|
147
|
+
export {
|
|
148
|
+
Settings as default
|
|
149
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
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 admin = require("@strapi/strapi/admin");
|
|
7
|
+
const PLUGIN_BASE = "/blogseo";
|
|
8
|
+
const extractErrorMessage = (error, fallback) => {
|
|
9
|
+
const data = error?.response?.data;
|
|
10
|
+
return data?.error?.message ?? fallback;
|
|
11
|
+
};
|
|
12
|
+
const Settings = () => {
|
|
13
|
+
const { get, post } = admin.useFetchClient();
|
|
14
|
+
const { toggleNotification } = admin.useNotification();
|
|
15
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
16
|
+
const [isSubmitting, setIsSubmitting] = react.useState(false);
|
|
17
|
+
const [status, setStatus] = react.useState(null);
|
|
18
|
+
const [contentTypes, setContentTypes] = react.useState([]);
|
|
19
|
+
const [apiKey, setApiKey] = react.useState("");
|
|
20
|
+
const [pluralApiId, setPluralApiId] = react.useState("");
|
|
21
|
+
react.useEffect(() => {
|
|
22
|
+
const load = async () => {
|
|
23
|
+
try {
|
|
24
|
+
const [statusRes, contentTypesRes] = await Promise.all([
|
|
25
|
+
get(`${PLUGIN_BASE}/status`),
|
|
26
|
+
get(`${PLUGIN_BASE}/content-types`)
|
|
27
|
+
]);
|
|
28
|
+
setStatus(statusRes.data);
|
|
29
|
+
setContentTypes(contentTypesRes.data.contentTypes ?? []);
|
|
30
|
+
if (contentTypesRes.data.contentTypes?.length)
|
|
31
|
+
setPluralApiId(contentTypesRes.data.contentTypes[0].pluralApiId);
|
|
32
|
+
} catch {
|
|
33
|
+
toggleNotification({ type: "danger", message: "Could not load BlogSEO settings." });
|
|
34
|
+
} finally {
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
load();
|
|
39
|
+
}, [get, toggleNotification]);
|
|
40
|
+
const handleConnect = async () => {
|
|
41
|
+
if (!apiKey.trim())
|
|
42
|
+
return toggleNotification({ type: "warning", message: "Paste your BlogSEO connection key first." });
|
|
43
|
+
if (!pluralApiId)
|
|
44
|
+
return toggleNotification({ type: "warning", message: "Pick a collection to publish into." });
|
|
45
|
+
setIsSubmitting(true);
|
|
46
|
+
try {
|
|
47
|
+
const { data } = await post(
|
|
48
|
+
`${PLUGIN_BASE}/connect`,
|
|
49
|
+
{ apiKey: apiKey.trim(), pluralApiId }
|
|
50
|
+
);
|
|
51
|
+
setStatus({ connected: true, siteUrl: data.siteUrl, pluralApiId });
|
|
52
|
+
setApiKey("");
|
|
53
|
+
toggleNotification({
|
|
54
|
+
type: "success",
|
|
55
|
+
message: "Connected to BlogSEO! Articles will now publish into this Strapi."
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
toggleNotification({
|
|
59
|
+
type: "danger",
|
|
60
|
+
message: extractErrorMessage(error, "Failed to connect to BlogSEO.")
|
|
61
|
+
});
|
|
62
|
+
} finally {
|
|
63
|
+
setIsSubmitting(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const handleDisconnect = async () => {
|
|
67
|
+
setIsSubmitting(true);
|
|
68
|
+
try {
|
|
69
|
+
await post(`${PLUGIN_BASE}/disconnect`, {});
|
|
70
|
+
setStatus({ connected: false, siteUrl: null, pluralApiId: null });
|
|
71
|
+
toggleNotification({ type: "success", message: "Disconnected from BlogSEO." });
|
|
72
|
+
} catch {
|
|
73
|
+
toggleNotification({ type: "danger", message: "Failed to disconnect." });
|
|
74
|
+
} finally {
|
|
75
|
+
setIsSubmitting(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
|
|
79
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
81
|
+
admin.Layouts.Header,
|
|
82
|
+
{
|
|
83
|
+
title: "BlogSEO",
|
|
84
|
+
subtitle: "Publish AI-generated SEO articles and build backlinks straight into this Strapi."
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "tableShadow", hasRadius: true, children: status?.connected ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
|
|
88
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "Connected to BlogSEO" }),
|
|
89
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
90
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "Publishing into " }),
|
|
91
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: status.pluralApiId ?? "—" }),
|
|
92
|
+
status.siteUrl ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "neutral600", children: [
|
|
93
|
+
" · ",
|
|
94
|
+
status.siteUrl
|
|
95
|
+
] }) : null
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: handleDisconnect, loading: isSubmitting, children: "Disconnect" }) })
|
|
98
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "Connect your BlogSEO account" }),
|
|
100
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
101
|
+
designSystem.Field.Root,
|
|
102
|
+
{
|
|
103
|
+
name: "apiKey",
|
|
104
|
+
hint: "Generate this in your BlogSEO dashboard → Integrations → Strapi → “Connect with the Strapi plugin”.",
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "BlogSEO connection key" }),
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
+
designSystem.Field.Input,
|
|
109
|
+
{
|
|
110
|
+
type: "password",
|
|
111
|
+
value: apiKey,
|
|
112
|
+
onChange: (event) => setApiKey(event.target.value),
|
|
113
|
+
placeholder: "blogseo_sp_..."
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
121
|
+
designSystem.Field.Root,
|
|
122
|
+
{
|
|
123
|
+
name: "contentType",
|
|
124
|
+
hint: "New articles from BlogSEO are created as entries here.",
|
|
125
|
+
children: [
|
|
126
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Collection to publish into" }),
|
|
127
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
128
|
+
designSystem.SingleSelect,
|
|
129
|
+
{
|
|
130
|
+
value: pluralApiId,
|
|
131
|
+
onChange: (value) => setPluralApiId(String(value)),
|
|
132
|
+
placeholder: "Select a collection",
|
|
133
|
+
children: contentTypes.map((contentType) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: contentType.pluralApiId, children: [
|
|
134
|
+
contentType.displayName,
|
|
135
|
+
" (",
|
|
136
|
+
contentType.pluralApiId,
|
|
137
|
+
")"
|
|
138
|
+
] }, contentType.uid))
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleConnect, loading: isSubmitting, disabled: !apiKey || !pluralApiId, children: "Connect" }) })
|
|
146
|
+
] }) }) })
|
|
147
|
+
] });
|
|
148
|
+
};
|
|
149
|
+
exports.default = Settings;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
5
|
+
const v = glob[path];
|
|
6
|
+
if (v) {
|
|
7
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
8
|
+
}
|
|
9
|
+
return new Promise((_, reject) => {
|
|
10
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
11
|
+
reject.bind(
|
|
12
|
+
null,
|
|
13
|
+
new Error(
|
|
14
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
const PLUGIN_ID = "blogseo";
|
|
21
|
+
const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
|
|
22
|
+
const Initializer = ({ setPlugin }) => {
|
|
23
|
+
const ref = react.useRef(setPlugin);
|
|
24
|
+
react.useEffect(() => {
|
|
25
|
+
ref.current(PLUGIN_ID);
|
|
26
|
+
}, []);
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
const plugin = {
|
|
30
|
+
register(app) {
|
|
31
|
+
app.createSettingSection(
|
|
32
|
+
{
|
|
33
|
+
id: PLUGIN_ID,
|
|
34
|
+
intlLabel: { id: getTranslation("settings.section"), defaultMessage: "BlogSEO" }
|
|
35
|
+
},
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
intlLabel: { id: getTranslation("settings.link"), defaultMessage: "Configuration" },
|
|
39
|
+
id: PLUGIN_ID,
|
|
40
|
+
to: `/settings/${PLUGIN_ID}`,
|
|
41
|
+
Component: () => Promise.resolve().then(() => require("./Settings-C3-8r0LW.js")),
|
|
42
|
+
permissions: []
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
);
|
|
46
|
+
app.registerPlugin({
|
|
47
|
+
id: PLUGIN_ID,
|
|
48
|
+
initializer: Initializer,
|
|
49
|
+
isReady: false,
|
|
50
|
+
name: PLUGIN_ID
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
registerTrads({ locales }) {
|
|
54
|
+
return Promise.all(
|
|
55
|
+
locales.map(async (locale) => {
|
|
56
|
+
try {
|
|
57
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-DXs_qePG.js")) }), `./translations/${locale}.json`, 3);
|
|
58
|
+
const newData = {};
|
|
59
|
+
for (const key of Object.keys(data)) newData[getTranslation(key)] = data[key];
|
|
60
|
+
return { data: newData, locale };
|
|
61
|
+
} catch {
|
|
62
|
+
return { data: {}, locale };
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.default = plugin;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
3
|
+
const v = glob[path];
|
|
4
|
+
if (v) {
|
|
5
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
6
|
+
}
|
|
7
|
+
return new Promise((_, reject) => {
|
|
8
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
9
|
+
reject.bind(
|
|
10
|
+
null,
|
|
11
|
+
new Error(
|
|
12
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const PLUGIN_ID = "blogseo";
|
|
19
|
+
const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
|
|
20
|
+
const Initializer = ({ setPlugin }) => {
|
|
21
|
+
const ref = useRef(setPlugin);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
ref.current(PLUGIN_ID);
|
|
24
|
+
}, []);
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
const plugin = {
|
|
28
|
+
register(app) {
|
|
29
|
+
app.createSettingSection(
|
|
30
|
+
{
|
|
31
|
+
id: PLUGIN_ID,
|
|
32
|
+
intlLabel: { id: getTranslation("settings.section"), defaultMessage: "BlogSEO" }
|
|
33
|
+
},
|
|
34
|
+
[
|
|
35
|
+
{
|
|
36
|
+
intlLabel: { id: getTranslation("settings.link"), defaultMessage: "Configuration" },
|
|
37
|
+
id: PLUGIN_ID,
|
|
38
|
+
to: `/settings/${PLUGIN_ID}`,
|
|
39
|
+
Component: () => import("./Settings-BJbpMBlx.mjs"),
|
|
40
|
+
permissions: []
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
);
|
|
44
|
+
app.registerPlugin({
|
|
45
|
+
id: PLUGIN_ID,
|
|
46
|
+
initializer: Initializer,
|
|
47
|
+
isReady: false,
|
|
48
|
+
name: PLUGIN_ID
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
registerTrads({ locales }) {
|
|
52
|
+
return Promise.all(
|
|
53
|
+
locales.map(async (locale) => {
|
|
54
|
+
try {
|
|
55
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-CEwYiwKv.mjs") }), `./translations/${locale}.json`, 3);
|
|
56
|
+
const newData = {};
|
|
57
|
+
for (const key of Object.keys(data)) newData[getTranslation(key)] = data[key];
|
|
58
|
+
return { data: newData, locale };
|
|
59
|
+
} catch {
|
|
60
|
+
return { data: {}, locale };
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export {
|
|
67
|
+
plugin as default
|
|
68
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_ID = "blogseo";
|