@kyro-cms/admin 0.12.5 → 0.12.7
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/index.cjs +32 -110
- package/dist/index.css +1 -1
- package/dist/index.d.cts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +32 -110
- package/package.json +7 -2
- package/src/components/AutoForm.tsx +35 -45
- package/src/components/DashboardMetrics.tsx +519 -290
- package/src/components/FieldRenderer.tsx +59 -47
- package/src/components/ListView.tsx +10 -5
- package/src/components/PluginsManager.tsx +37 -20
- package/src/components/Sidebar.astro +7 -14
- package/src/components/blocks/AccordionBlock.tsx +8 -4
- package/src/components/blocks/ArrayBlock.tsx +4 -3
- package/src/components/blocks/BlockEditModal.tsx +4 -3
- package/src/components/blocks/CardBlock.tsx +10 -2
- package/src/components/blocks/ChildBlocksTree.tsx +19 -18
- package/src/components/blocks/CodeBlock.tsx +7 -2
- package/src/components/blocks/FileBlock.tsx +6 -2
- package/src/components/blocks/GenericBlock.tsx +1 -1
- package/src/components/blocks/HeadingBlock.tsx +6 -2
- package/src/components/blocks/HeadingSubheadingBlock.tsx +7 -2
- package/src/components/blocks/HeroBlock.tsx +12 -3
- package/src/components/blocks/ImageBlock.tsx +8 -2
- package/src/components/blocks/ListBlock.tsx +6 -2
- package/src/components/blocks/ParagraphBlock.tsx +2 -2
- package/src/components/blocks/RelationshipBlock.tsx +10 -2
- package/src/components/blocks/VideoBlock.tsx +7 -2
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +55 -58
- package/src/components/fields/BlocksField.tsx +1 -1
- package/src/components/fields/ChildrenField.tsx +3 -2
- package/src/components/fields/CodeField.tsx +3 -2
- package/src/components/fields/ColumnsField.tsx +3 -2
- package/src/components/fields/DateField.tsx +2 -2
- package/src/components/fields/FieldLayout.tsx +3 -3
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/MarkdownField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -4
- package/src/components/fields/RelationshipField.tsx +4 -1
- package/src/components/fields/RichTextField.tsx +200 -5
- package/src/components/fields/extensions/blockComponents.tsx +1 -1
- package/src/components/fields/extensions/blocksStore.ts +15 -12
- package/src/components/ui/Pagination.tsx +1 -1
- package/src/fields/index.ts +1 -1
- package/src/hooks/useAutoFormState.ts +7 -6
- package/src/index.ts +0 -1
- package/src/integration.ts +64 -26
- package/src/lib/api.ts +1 -0
- package/src/lib/config.ts +6 -19
- package/src/lib/core-types.ts +78 -0
- package/src/plugins/seo-admin.tsx +155 -0
- package/src/styles/main.css +2 -0
- package/src/vite-env.d.ts +10 -1
package/src/integration.ts
CHANGED
|
@@ -70,7 +70,7 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
70
70
|
sourcemap: false,
|
|
71
71
|
loader: { '.ts': 'ts', '.tsx': 'tsx' },
|
|
72
72
|
resolveExtensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'],
|
|
73
|
-
external: ['@kyro-cms/*'],
|
|
73
|
+
external: ['@kyro-cms/*', '@ai-sdk/*'],
|
|
74
74
|
});
|
|
75
75
|
tmpFile = resolvedConfig.replace(/\.ts$/, ".admin.mjs");
|
|
76
76
|
fs.writeFileSync(tmpFile, result.outputFiles[0].text, "utf8");
|
|
@@ -101,6 +101,10 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
101
101
|
collections: serialize(cfg?.collections) || [],
|
|
102
102
|
globals: serialize(cfg?.globals) || [],
|
|
103
103
|
collectionOverrides: serialize(cfg?.admin?.collectionOverrides) || {},
|
|
104
|
+
plugins: (cfg?.plugins || []).map((p: any) => ({
|
|
105
|
+
name: p.name,
|
|
106
|
+
adminEntry: p.adminEntry
|
|
107
|
+
}))
|
|
104
108
|
});
|
|
105
109
|
}).catch(err => {
|
|
106
110
|
parentPort.postMessage({ error: err.message });
|
|
@@ -128,17 +132,21 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
128
132
|
{ name: "metaDescription", type: "textarea", label: "Meta Description", admin: { description: "A brief summary for search engines (recommended < 160 chars).", autoGenerate: "content" } },
|
|
129
133
|
{ name: "keywords", type: "text", label: "Keywords", admin: { description: "Comma-separated list of keywords for this page." } },
|
|
130
134
|
{ name: "ogImage", type: "upload", label: "OpenGraph Image", relationTo: "media", admin: { description: "The image shown when shared on social media." } },
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
{
|
|
136
|
+
name: "twitter", type: "group", label: "Twitter Card", fields: [
|
|
137
|
+
{ name: "title", type: "text", label: "Twitter Title" },
|
|
138
|
+
{ name: "description", type: "textarea", label: "Twitter Description" },
|
|
139
|
+
{ name: "image", type: "upload", label: "Twitter Image", relationTo: "media" },
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "advanced", type: "group", label: "Advanced Search Settings", fields: [
|
|
144
|
+
{ name: "noindex", type: "checkbox", label: "Hide from search engines (noindex)", defaultValue: false },
|
|
145
|
+
{ name: "nofollow", type: "checkbox", label: "Do not follow links (nofollow)", defaultValue: false },
|
|
146
|
+
{ name: "canonicalUrl", type: "text", label: "Canonical URL Override", admin: { description: "Leave empty to use the default canonical URL." } },
|
|
147
|
+
{ name: "structuredData", type: "code", label: "JSON-LD Structured Data", admin: { description: "Custom JSON-LD schema for this specific page." } },
|
|
148
|
+
]
|
|
149
|
+
},
|
|
142
150
|
];
|
|
143
151
|
for (const col of (configResult.collections || [])) {
|
|
144
152
|
if (col.seo) {
|
|
@@ -165,6 +173,36 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
165
173
|
vite: {
|
|
166
174
|
plugins: [
|
|
167
175
|
useSyncExternalStoreShimPlugin(),
|
|
176
|
+
{
|
|
177
|
+
name: "kyro-plugins-virtual",
|
|
178
|
+
resolveId(id: string) {
|
|
179
|
+
if (id === "virtual:kyro-plugins") {
|
|
180
|
+
return "\0virtual:kyro-plugins";
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
load(id: string) {
|
|
184
|
+
if (id === "\0virtual:kyro-plugins") {
|
|
185
|
+
try {
|
|
186
|
+
const configData = JSON.parse(fs.readFileSync(configFile, "utf8"));
|
|
187
|
+
const pluginsWithAdmin = (configData.plugins || []).filter((p: any) => p.adminEntry);
|
|
188
|
+
|
|
189
|
+
let imports = `import { lazy } from 'react';\n`;
|
|
190
|
+
let views = `export const pluginViews = {\n`;
|
|
191
|
+
|
|
192
|
+
for (const p of pluginsWithAdmin) {
|
|
193
|
+
// Vite requires absolute paths for local files or bare specifiers for packages
|
|
194
|
+
views += ` '${p.name}': lazy(() => import('${p.adminEntry}')),\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
views += `};\n`;
|
|
198
|
+
const configExport = `export const projectConfig = ${JSON.stringify(configData)};\n`;
|
|
199
|
+
return imports + views + configExport;
|
|
200
|
+
} catch (e) {
|
|
201
|
+
return `export const pluginViews = {};\nexport const projectConfig = null;\n`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
168
206
|
{
|
|
169
207
|
name: "kyro-admin-tsx-loader",
|
|
170
208
|
enforce: "pre" as const,
|
|
@@ -235,22 +273,22 @@ export default debug;
|
|
|
235
273
|
optimizeDeps: {
|
|
236
274
|
include: [
|
|
237
275
|
'@kyro-cms/admin',
|
|
238
|
-
'recharts',
|
|
239
|
-
'recharts > recharts-scale',
|
|
240
|
-
'recharts > recharts-scale > decimal.js-light',
|
|
241
|
-
'recharts > react-smooth',
|
|
242
|
-
'recharts > react-smooth > react-transition-group',
|
|
243
|
-
'
|
|
244
|
-
'
|
|
245
|
-
'react-i18next > html-parse-stringify > void-elements',
|
|
246
|
-
'
|
|
276
|
+
'@kyro-cms/admin > recharts',
|
|
277
|
+
'@kyro-cms/admin > recharts > recharts-scale',
|
|
278
|
+
'@kyro-cms/admin > recharts > recharts-scale > decimal.js-light',
|
|
279
|
+
'@kyro-cms/admin > recharts > react-smooth',
|
|
280
|
+
'@kyro-cms/admin > recharts > react-smooth > react-transition-group',
|
|
281
|
+
'@kyro-cms/admin > react-i18next',
|
|
282
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify',
|
|
283
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify > void-elements',
|
|
284
|
+
'@kyro-cms/admin > use-sync-external-store',
|
|
285
|
+
'@kyro-cms/admin > use-sync-external-store/with-selector.js',
|
|
247
286
|
],
|
|
248
287
|
needsInterop: [
|
|
249
|
-
'decimal.js-light',
|
|
250
|
-
'
|
|
251
|
-
'use-sync-external-store
|
|
252
|
-
'
|
|
253
|
-
'react-i18next > html-parse-stringify',
|
|
288
|
+
'@kyro-cms/admin > recharts > recharts-scale > decimal.js-light',
|
|
289
|
+
'@kyro-cms/admin > react-i18next > html-parse-stringify > void-elements',
|
|
290
|
+
'@kyro-cms/admin > use-sync-external-store',
|
|
291
|
+
'@kyro-cms/admin > use-sync-external-store/with-selector.js',
|
|
254
292
|
],
|
|
255
293
|
exclude: ['debug', 'react/compiler-runtime'],
|
|
256
294
|
},
|
package/src/lib/api.ts
CHANGED
|
@@ -35,6 +35,7 @@ export async function fetchWithAuth(
|
|
|
35
35
|
if (!(options.body instanceof FormData)) {
|
|
36
36
|
headers["Content-Type"] = "application/json";
|
|
37
37
|
}
|
|
38
|
+
headers["x-kyro-admin"] = "true";
|
|
38
39
|
Object.assign(headers, options.headers as Record<string, string> | undefined);
|
|
39
40
|
|
|
40
41
|
const response = await fetch(resolveApi(url), {
|
package/src/lib/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core/client";
|
|
2
|
+
import type { AdminConfig } from "./core-types";
|
|
2
3
|
import {
|
|
3
4
|
blogCollections,
|
|
4
5
|
ecommerceCollections,
|
|
@@ -10,11 +11,7 @@ import {
|
|
|
10
11
|
allSettingsGlobals,
|
|
11
12
|
coreSettingsGlobals,
|
|
12
13
|
} from "@kyro-cms/core/templates";
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
// Injected by Vite's define config — exact path to the serialized config JSON.
|
|
16
|
-
declare const __KYRO_ADMIN_CONFIG_FILE__: string;
|
|
17
|
-
|
|
14
|
+
import { projectConfig } from "virtual:kyro-plugins";
|
|
18
15
|
type ConfigCollectionInput =
|
|
19
16
|
| CollectionConfig[]
|
|
20
17
|
| Record<string, CollectionConfig>
|
|
@@ -255,26 +252,16 @@ function createProjectAdminConfig(config: {
|
|
|
255
252
|
};
|
|
256
253
|
}
|
|
257
254
|
|
|
258
|
-
// Read config from the
|
|
259
|
-
let cachedConfig: { collections: any[]; globals: any[]; collectionOverrides?: Record<string, any> } | null = null;
|
|
260
|
-
|
|
255
|
+
// Read config from the virtual module injected by our Vite plugin
|
|
261
256
|
function loadProjectConfig() {
|
|
262
|
-
if (
|
|
263
|
-
try {
|
|
264
|
-
if (typeof __KYRO_ADMIN_CONFIG_FILE__ === "string" && fs.existsSync(__KYRO_ADMIN_CONFIG_FILE__)) {
|
|
265
|
-
cachedConfig = JSON.parse(fs.readFileSync(__KYRO_ADMIN_CONFIG_FILE__, "utf8"));
|
|
266
|
-
return cachedConfig;
|
|
267
|
-
}
|
|
268
|
-
} catch {
|
|
269
|
-
// fall through to kitchen-sink default
|
|
270
|
-
}
|
|
257
|
+
if (projectConfig) return projectConfig;
|
|
271
258
|
return null;
|
|
272
259
|
}
|
|
273
260
|
|
|
274
261
|
const projectCfg = loadProjectConfig() || { collections: [], globals: [] };
|
|
275
262
|
|
|
276
263
|
const rawConfig = createProjectAdminConfig(projectCfg);
|
|
277
|
-
applyCollectionAdminOverrides(rawConfig.collections, projectCfg.collectionOverrides);
|
|
264
|
+
applyCollectionAdminOverrides(rawConfig.collections, (projectCfg.collectionOverrides as Record<string, any>) || {});
|
|
278
265
|
|
|
279
266
|
export const adminConfig = rawConfig;
|
|
280
267
|
export const collections = adminConfig.collections;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local type declarations for core types that TSC cannot resolve
|
|
3
|
+
* from the barrel `@kyro-cms/core/client` due to tsup's chunked
|
|
4
|
+
* .d.ts re-export format.
|
|
5
|
+
*
|
|
6
|
+
* These types mirror the definitions in the core source and are
|
|
7
|
+
* used only for compile-time type checking in the admin package.
|
|
8
|
+
* At runtime, Vite resolves the actual types correctly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Minimal BaseField shape needed for IconField/SecretField */
|
|
12
|
+
interface BaseField {
|
|
13
|
+
type: string;
|
|
14
|
+
name: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
admin?: Record<string, any>;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --- Field Types ---
|
|
22
|
+
|
|
23
|
+
export interface IconField extends BaseField {
|
|
24
|
+
type: "icon";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SecretField extends BaseField {
|
|
28
|
+
type: "secret";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DeclarativeCondition =
|
|
32
|
+
| {
|
|
33
|
+
field: string;
|
|
34
|
+
equals?: string | number | boolean;
|
|
35
|
+
notEquals?: string | number | boolean;
|
|
36
|
+
in?: (string | number | boolean)[];
|
|
37
|
+
greaterThan?: number;
|
|
38
|
+
}
|
|
39
|
+
| { and: DeclarativeCondition[] }
|
|
40
|
+
| { or: DeclarativeCondition[] };
|
|
41
|
+
|
|
42
|
+
// --- Config Types ---
|
|
43
|
+
|
|
44
|
+
export interface AdminConfig {
|
|
45
|
+
/** Admin panel title */
|
|
46
|
+
title?: string;
|
|
47
|
+
/** Sidebar label override */
|
|
48
|
+
label?: string;
|
|
49
|
+
/** Description shown in the admin */
|
|
50
|
+
description?: string;
|
|
51
|
+
/** Admin panel icon (lucide icon name) */
|
|
52
|
+
icon?: string;
|
|
53
|
+
/** Hide from navigation */
|
|
54
|
+
hidden?: boolean;
|
|
55
|
+
/** Default columns shown in the list view */
|
|
56
|
+
listColumns?: string[];
|
|
57
|
+
/** Default sort field */
|
|
58
|
+
defaultSort?: string;
|
|
59
|
+
/** Enable/disable search */
|
|
60
|
+
enableSearch?: boolean;
|
|
61
|
+
/** Custom admin group */
|
|
62
|
+
group?: string;
|
|
63
|
+
/** Pagination settings */
|
|
64
|
+
pagination?: {
|
|
65
|
+
defaultLimit?: number;
|
|
66
|
+
limits?: number[];
|
|
67
|
+
};
|
|
68
|
+
/** Preview URL function */
|
|
69
|
+
preview?: (doc: Record<string, any>) => string | null;
|
|
70
|
+
/** Live preview configuration */
|
|
71
|
+
livePreview?: {
|
|
72
|
+
url?: string | ((doc: Record<string, any>) => string);
|
|
73
|
+
};
|
|
74
|
+
/** Custom CSS class for the admin view */
|
|
75
|
+
className?: string;
|
|
76
|
+
/** Disable create button */
|
|
77
|
+
disableCreate?: boolean;
|
|
78
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useToast } from '../index';
|
|
3
|
+
|
|
4
|
+
export default function SeoSettings() {
|
|
5
|
+
const [loading, setLoading] = useState(true);
|
|
6
|
+
const [saving, setSaving] = useState(false);
|
|
7
|
+
const [docId, setDocId] = useState<string | null>(null);
|
|
8
|
+
|
|
9
|
+
const [sitemap, setSitemap] = useState(true);
|
|
10
|
+
const [robotsTxt, setRobotsTxt] = useState('');
|
|
11
|
+
const [canonicalUrl, setCanonicalUrl] = useState('');
|
|
12
|
+
const [ogImage, setOgImage] = useState('');
|
|
13
|
+
|
|
14
|
+
const { addToast } = useToast();
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const apiPath = (window as any).__KYRO_API_PATH__ || '/api';
|
|
18
|
+
fetch(`${apiPath}/seo-settings?limit=1`, { credentials: 'include' })
|
|
19
|
+
.then(res => res.json())
|
|
20
|
+
.then(resData => {
|
|
21
|
+
if (resData && resData.docs && resData.docs.length > 0) {
|
|
22
|
+
const doc = resData.docs[0];
|
|
23
|
+
setDocId(doc.id);
|
|
24
|
+
if (doc.sitemap !== undefined) setSitemap(doc.sitemap);
|
|
25
|
+
if (doc.robotsTxt !== undefined) setRobotsTxt(doc.robotsTxt);
|
|
26
|
+
if (doc.canonicalUrl !== undefined) setCanonicalUrl(doc.canonicalUrl);
|
|
27
|
+
if (doc.ogImage !== undefined) setOgImage(doc.ogImage);
|
|
28
|
+
}
|
|
29
|
+
setLoading(false);
|
|
30
|
+
})
|
|
31
|
+
.catch(err => {
|
|
32
|
+
console.error('Failed to load SEO settings:', err);
|
|
33
|
+
setLoading(false);
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const handleSave = async (e: React.FormEvent) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
setSaving(true);
|
|
40
|
+
|
|
41
|
+
const apiPath = (window as any).__KYRO_API_PATH__ || '/api';
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const payload = {
|
|
45
|
+
sitemap,
|
|
46
|
+
robotsTxt,
|
|
47
|
+
canonicalUrl,
|
|
48
|
+
ogImage
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
let url = `${apiPath}/seo-settings`;
|
|
52
|
+
let method = 'POST';
|
|
53
|
+
|
|
54
|
+
if (docId) {
|
|
55
|
+
url = `${url}/${docId}`;
|
|
56
|
+
method = 'PATCH';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const res = await fetch(url, {
|
|
60
|
+
method,
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(payload),
|
|
63
|
+
credentials: 'include'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
throw new Error('Failed to save settings');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
if (data.doc && !docId) {
|
|
72
|
+
setDocId(data.doc.id);
|
|
73
|
+
}
|
|
74
|
+
addToast('success', 'SEO Settings saved successfully!');
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(err);
|
|
77
|
+
addToast('error', 'Error saving settings.');
|
|
78
|
+
} finally {
|
|
79
|
+
setSaving(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (loading) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="p-8 flex items-center justify-center animate-pulse">
|
|
86
|
+
<div className="w-8 h-8 rounded-full border-2 border-t-[var(--kyro-primary)] border-[var(--kyro-primary)]/20 animate-spin" />
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="p-4">
|
|
93
|
+
<h2 className="text-lg font-bold mb-4">SEO Configuration</h2>
|
|
94
|
+
<form onSubmit={handleSave} className="space-y-4">
|
|
95
|
+
|
|
96
|
+
{/* Sitemap Checkbox */}
|
|
97
|
+
<div className="flex items-center gap-3">
|
|
98
|
+
<input
|
|
99
|
+
type="checkbox"
|
|
100
|
+
id="sitemap"
|
|
101
|
+
checked={sitemap}
|
|
102
|
+
onChange={(e) => setSitemap(e.target.checked)}
|
|
103
|
+
className="w-4 h-4 rounded border-[var(--kyro-border)] text-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)]"
|
|
104
|
+
/>
|
|
105
|
+
<label htmlFor="sitemap" className="text-sm font-medium text-[var(--kyro-text)]">
|
|
106
|
+
Enable Sitemap Generation
|
|
107
|
+
</label>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* Canonical URL */}
|
|
111
|
+
<div className="space-y-1.5">
|
|
112
|
+
<label className="text-xs font-medium text-[var(--kyro-text)]">Canonical URL</label>
|
|
113
|
+
<input
|
|
114
|
+
type="url"
|
|
115
|
+
value={canonicalUrl}
|
|
116
|
+
onChange={(e) => setCanonicalUrl(e.target.value)}
|
|
117
|
+
className="w-full px-3 py-1.5 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-lg text-sm focus:border-[var(--kyro-primary)] focus:ring-1 focus:ring-[var(--kyro-primary)] outline-none transition-all"
|
|
118
|
+
placeholder="https://example.com"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Default OG Image */}
|
|
123
|
+
<div className="space-y-1.5">
|
|
124
|
+
<label className="text-xs font-medium text-[var(--kyro-text)]">Default OG Image URL</label>
|
|
125
|
+
<input
|
|
126
|
+
type="text"
|
|
127
|
+
value={ogImage}
|
|
128
|
+
onChange={(e) => setOgImage(e.target.value)}
|
|
129
|
+
className="w-full px-3 py-1.5 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-lg text-sm focus:border-[var(--kyro-primary)] focus:ring-1 focus:ring-[var(--kyro-primary)] outline-none transition-all"
|
|
130
|
+
placeholder="/images/og-default.jpg"
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* robots.txt Content */}
|
|
135
|
+
<div className="space-y-1.5">
|
|
136
|
+
<label className="text-xs font-medium text-[var(--kyro-text)]">robots.txt Content</label>
|
|
137
|
+
<textarea
|
|
138
|
+
value={robotsTxt}
|
|
139
|
+
onChange={(e) => setRobotsTxt(e.target.value)}
|
|
140
|
+
className="w-full h-24 px-3 py-2 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-lg text-sm focus:border-[var(--kyro-primary)] focus:ring-1 focus:ring-[var(--kyro-primary)] outline-none transition-all font-mono"
|
|
141
|
+
placeholder="User-agent: * Allow: /"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<button
|
|
146
|
+
type="submit"
|
|
147
|
+
disabled={saving}
|
|
148
|
+
className="w-full py-2 mt-2 bg-[var(--kyro-primary)] text-white font-bold rounded-lg shadow-md shadow-[var(--kyro-primary)]/20 hover:opacity-90 transition-opacity disabled:opacity-50 text-sm"
|
|
149
|
+
>
|
|
150
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
151
|
+
</button>
|
|
152
|
+
</form>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
package/src/styles/main.css
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
/* Semantic UI */
|
|
41
41
|
--kyro-border: #f3f4f6;
|
|
42
|
+
--kyro-border-strong: #d1d5db;
|
|
42
43
|
--kyro-input-bg: #ffffff;
|
|
43
44
|
--kyro-input-border: #e5e7eb;
|
|
44
45
|
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
--kyro-text-muted: #71717a;
|
|
92
93
|
|
|
93
94
|
--kyro-border: rgba(255, 255, 255, 0.08);
|
|
95
|
+
--kyro-border-strong: rgba(255, 255, 255, 0.18);
|
|
94
96
|
--kyro-input-bg: #1c1c1c;
|
|
95
97
|
--kyro-input-border: rgba(255, 255, 255, 0.1);
|
|
96
98
|
|
package/src/vite-env.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ declare module "graphiql/graphiql.css" {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
declare module "react-image-crop/dist/ReactCrop.css" {
|
|
12
|
-
const content: string
|
|
12
|
+
const content: Record<string, string>;
|
|
13
13
|
export default content;
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
declare module "virtual:kyro-plugins" {
|
|
17
|
+
import type { LazyExoticComponent, ComponentType } from "react";
|
|
18
|
+
export const pluginViews: Record<
|
|
19
|
+
string,
|
|
20
|
+
LazyExoticComponent<ComponentType<any>>
|
|
21
|
+
>;
|
|
22
|
+
export const projectConfig: any;
|
|
23
|
+
}
|