@kyro-cms/admin 0.9.0 → 0.9.2
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 +11715 -11292
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11326 -10912
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/src/components/ActionBar.tsx +25 -161
- package/src/components/Admin.tsx +2 -4
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +572 -461
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +52 -65
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +57 -216
- package/src/components/MediaGallery.tsx +334 -367
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +59 -52
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +433 -55
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +155 -90
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +10 -13
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +110 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +187 -196
- package/src/hooks/useQueue.ts +60 -0
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +67 -7
- package/src/lib/autoform-store.ts +90 -53
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +48 -11
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/auth/register.astro +5 -2
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
package/src/integration.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { AstroIntegration } from "astro";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
import { execSync } from "child_process";
|
|
5
4
|
import { pathToFileURL } from "url";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
6
|
import { config as loadDotEnv } from "dotenv";
|
|
7
|
-
import {
|
|
7
|
+
import { Worker } from "worker_threads";
|
|
8
|
+
|
|
9
|
+
const _shimDir = path.resolve(new URL(".", import.meta.url).pathname, "lib/shim");
|
|
10
|
+
const _shimUses = path.join(_shimDir, "use-sync-external-store.js");
|
|
11
|
+
const _shimUsesWs = path.join(_shimDir, "use-sync-external-store-with-selector.js");
|
|
8
12
|
|
|
9
13
|
export interface KyroAdminOptions {
|
|
10
14
|
basePath?: string;
|
|
@@ -46,81 +50,178 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
46
50
|
logger.warn(`Config file not found. Using defaults.`);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
// Load .env first since Vite hasn't processed it yet at this point
|
|
53
|
-
// in the lifecycle, and the config module evaluates eagerly.
|
|
54
|
-
// Use esbuild to transpile TS to ESM, then evaluate in a child
|
|
55
|
-
// process to completely bypass Vite's module runner interception.
|
|
53
|
+
// Transpile the user's config and write a serialized JSON copy.
|
|
54
|
+
// The JSON path is injected via Vite's define so the admin can read it reliably.
|
|
55
|
+
const configFile = path.join(path.dirname(resolvedConfig), ".kyro-admin-config.json");
|
|
56
56
|
let tmpFile = "";
|
|
57
57
|
try {
|
|
58
58
|
const envPath = path.join(path.dirname(resolvedConfig), ".env");
|
|
59
59
|
if (fs.existsSync(envPath)) {
|
|
60
60
|
loadDotEnv({ path: envPath });
|
|
61
61
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const result = await build({
|
|
63
|
+
entryPoints: [resolvedConfig],
|
|
64
|
+
bundle: true,
|
|
65
65
|
format: "esm",
|
|
66
|
+
platform: "node",
|
|
66
67
|
target: "es2022",
|
|
68
|
+
write: false,
|
|
67
69
|
sourcemap: false,
|
|
70
|
+
loader: { '.ts': 'ts', '.tsx': 'tsx' },
|
|
71
|
+
resolveExtensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'],
|
|
72
|
+
external: ['@kyro-cms/*'],
|
|
68
73
|
});
|
|
69
|
-
// Write transpiled config alongside original so Node.js can
|
|
70
|
-
// resolve @kyro-cms/core from the project's node_modules
|
|
71
74
|
tmpFile = resolvedConfig.replace(/\.ts$/, ".admin.mjs");
|
|
72
|
-
fs.writeFileSync(tmpFile, result.
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
fs.writeFileSync(tmpFile, result.outputFiles[0].text, "utf8");
|
|
76
|
+
|
|
77
|
+
// Use a Worker thread to load the config in an isolated context.
|
|
78
|
+
const workerCode = `
|
|
79
|
+
import { parentPort } from 'worker_threads';
|
|
80
|
+
import('${pathToFileURL(tmpFile).href}').then(mod => {
|
|
81
|
+
const cfg = mod.default || mod;
|
|
82
|
+
const serialize = (obj) => {
|
|
83
|
+
if (obj === null || obj === undefined) return obj;
|
|
84
|
+
if (typeof obj === 'function') return undefined;
|
|
85
|
+
if (Array.isArray(obj)) return obj.map(serialize);
|
|
86
|
+
if (typeof obj === 'object') {
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
89
|
+
const sv = serialize(v);
|
|
90
|
+
if (sv !== undefined) result[k] = sv;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
return obj;
|
|
95
|
+
};
|
|
96
|
+
parentPort.postMessage({
|
|
97
|
+
collections: serialize(cfg?.collections) || [],
|
|
98
|
+
globals: serialize(cfg?.globals) || [],
|
|
99
|
+
collectionOverrides: serialize(cfg?.admin?.collectionOverrides) || {},
|
|
100
|
+
});
|
|
101
|
+
}).catch(err => {
|
|
102
|
+
parentPort.postMessage({ error: err.message });
|
|
103
|
+
});
|
|
104
|
+
`;
|
|
105
|
+
const worker = new Worker(workerCode, { eval: true, env: { ...process.env, NODE_OPTIONS: '' } });
|
|
106
|
+
const configResult = await new Promise<any>((resolve, reject) => {
|
|
107
|
+
worker.on("message", resolve);
|
|
108
|
+
worker.on("error", reject);
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
worker.terminate();
|
|
111
|
+
reject(new Error("Config loading timed out"));
|
|
112
|
+
}, 30000);
|
|
113
|
+
});
|
|
114
|
+
worker.terminate();
|
|
115
|
+
|
|
116
|
+
if (configResult.error) {
|
|
117
|
+
throw new Error(configResult.error);
|
|
93
118
|
}
|
|
94
|
-
(
|
|
95
|
-
collections: configModule.collections,
|
|
96
|
-
globals: configModule.globals,
|
|
97
|
-
adapter: configModule.adapter || null,
|
|
98
|
-
};
|
|
119
|
+
fs.writeFileSync(configFile, JSON.stringify(configResult, null, 2), "utf8");
|
|
99
120
|
logger.info("Project config loaded for admin");
|
|
100
121
|
} catch (e: any) {
|
|
101
|
-
logger.
|
|
122
|
+
logger.error(`Could not load project config: ${e.message}`);
|
|
102
123
|
} finally {
|
|
103
|
-
|
|
104
|
-
const f = resolvedConfig.replace(/\.ts$/, suffix);
|
|
105
|
-
if (fs.existsSync(f)) { try { fs.unlinkSync(f); } catch { /* ignore */ } }
|
|
106
|
-
}
|
|
124
|
+
if (tmpFile && fs.existsSync(tmpFile)) { try { fs.unlinkSync(tmpFile); } catch { /* ignore */ } }
|
|
107
125
|
}
|
|
108
126
|
|
|
109
|
-
// Set up Vite aliases and
|
|
127
|
+
// Set up Vite aliases, defines, and plugins for runtime use
|
|
110
128
|
updateConfig({
|
|
111
129
|
vite: {
|
|
130
|
+
plugins: [
|
|
131
|
+
{
|
|
132
|
+
name: "kyro-admin-tsx-loader",
|
|
133
|
+
enforce: "pre" as const,
|
|
134
|
+
config(_config: any) {
|
|
135
|
+
const existingEsbuild = _config.esbuild;
|
|
136
|
+
const existingExclude = existingEsbuild?.exclude;
|
|
137
|
+
const adminInclude = /\/node_modules\/(?!.*@kyro-cms\/(admin|core))/;
|
|
138
|
+
return {
|
|
139
|
+
esbuild: {
|
|
140
|
+
...(existingEsbuild || {}),
|
|
141
|
+
exclude: existingExclude
|
|
142
|
+
? Array.isArray(existingExclude)
|
|
143
|
+
? [...existingExclude, adminInclude]
|
|
144
|
+
: [existingExclude, adminInclude]
|
|
145
|
+
: adminInclude,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "kyro-cjs-shim",
|
|
152
|
+
enforce: "pre" as const,
|
|
153
|
+
resolveId(id: string) {
|
|
154
|
+
if (id.includes('react/compiler-runtime')) {
|
|
155
|
+
return "\0react-compiler-runtime";
|
|
156
|
+
}
|
|
157
|
+
if (id === 'debug' || id.includes('debug/src/browser.js')) {
|
|
158
|
+
return "\0debug-browser";
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
load(id: string) {
|
|
162
|
+
if (id === "\0react-compiler-runtime") {
|
|
163
|
+
return `
|
|
164
|
+
import React from "react";
|
|
165
|
+
export function c(size) {
|
|
166
|
+
const internals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
167
|
+
if (!internals || !internals.H) {
|
|
168
|
+
return new Array(size);
|
|
169
|
+
}
|
|
170
|
+
return internals.H.useMemoCache(size);
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
if (id === "\0debug-browser") {
|
|
175
|
+
return `
|
|
176
|
+
function debug(namespace) {
|
|
177
|
+
function d(...args) {
|
|
178
|
+
if (typeof localStorage !== "undefined" && localStorage.getItem("DEBUG")) {
|
|
179
|
+
console.log(namespace, ...args);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
d.enabled = false;
|
|
183
|
+
return d;
|
|
184
|
+
}
|
|
185
|
+
debug.enable = function() {};
|
|
186
|
+
debug.disable = function() {};
|
|
187
|
+
debug.enabled = function() { return false; };
|
|
188
|
+
export default debug;
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
112
194
|
resolve: {
|
|
113
195
|
alias: {
|
|
114
196
|
"kyro:config": resolvedConfig,
|
|
115
197
|
},
|
|
116
198
|
},
|
|
199
|
+
optimizeDeps: {
|
|
200
|
+
include: [
|
|
201
|
+
'use-sync-external-store',
|
|
202
|
+
],
|
|
203
|
+
exclude: ['debug', 'react/compiler-runtime'],
|
|
204
|
+
},
|
|
117
205
|
define: {
|
|
118
206
|
__KYRO_ADMIN_PATH__: JSON.stringify(basePath),
|
|
119
207
|
__KYRO_API_PATH__: JSON.stringify(apiPath),
|
|
208
|
+
__KYRO_ADMIN_CONFIG_FILE__: JSON.stringify(configFile),
|
|
209
|
+
},
|
|
210
|
+
ssr: {
|
|
211
|
+
noExternal: ['@kyro-cms/admin', '@kyro-cms/core'],
|
|
120
212
|
},
|
|
121
213
|
},
|
|
122
214
|
});
|
|
123
215
|
|
|
216
|
+
// Load the user's config at runtime via the kyro:config alias.
|
|
217
|
+
// We set a placeholder here; the actual config is loaded by
|
|
218
|
+
// admin/lib/config.ts via dynamic import of kyro:config.
|
|
219
|
+
(globalThis as any).__KYRO_ADMIN_PROJECT_CONFIG__ = {
|
|
220
|
+
collections: [],
|
|
221
|
+
globals: [],
|
|
222
|
+
adapter: null,
|
|
223
|
+
};
|
|
224
|
+
|
|
124
225
|
// Inject Admin UI Routes
|
|
125
226
|
const pages = [
|
|
126
227
|
{ pattern: "", entrypoint: "./pages/index.astro" },
|
|
@@ -128,6 +229,7 @@ export function kyroAdmin(options: KyroAdminOptions = {}): AstroIntegration {
|
|
|
128
229
|
{ pattern: "/register", entrypoint: "./pages/auth/register.astro" },
|
|
129
230
|
{ pattern: "/media", entrypoint: "./pages/media.astro" },
|
|
130
231
|
{ pattern: "/users", entrypoint: "./pages/users/index.astro" },
|
|
232
|
+
{ pattern: "/users/new", entrypoint: "./pages/users/new.astro" },
|
|
131
233
|
{ pattern: "/users/[id]", entrypoint: "./pages/users/[id].astro" },
|
|
132
234
|
{ pattern: "/roles", entrypoint: "./pages/roles/index.astro" },
|
|
133
235
|
{ pattern: "/settings", entrypoint: "./pages/settings/index.astro" },
|
package/src/kyro-cms.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ admin?: {
|
|
|
57
57
|
export interface SelectField extends FieldConfig { type: 'select'; options?: { label: string; value: string | number }[] }
|
|
58
58
|
export interface TextareaField extends FieldConfig { type: 'textarea' }
|
|
59
59
|
export interface MarkdownField extends FieldConfig { type: 'markdown' }
|
|
60
|
-
export interface RichTextField extends FieldConfig { type: '
|
|
60
|
+
export interface RichTextField extends FieldConfig { type: 'richtext' }
|
|
61
61
|
export interface CodeField extends FieldConfig { type: 'code'; language?: string }
|
|
62
62
|
export interface JSONField extends FieldConfig { type: 'json' }
|
|
63
63
|
export interface ImageField extends FieldConfig { type: 'image' }
|
|
@@ -208,4 +208,9 @@ declare module 'kyro:config' {
|
|
|
208
208
|
import type { KyroConfig } from '@kyro-cms/core';
|
|
209
209
|
const config: KyroConfig;
|
|
210
210
|
export default config;
|
|
211
|
-
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Injected by Vite's define config during admin integration setup
|
|
214
|
+
declare const __KYRO_ADMIN_CONFIG_FILE__: string;
|
|
215
|
+
declare const __KYRO_ADMIN_PATH__: string;
|
|
216
|
+
declare const __KYRO_API_PATH__: string;
|
|
@@ -7,7 +7,7 @@ import { adminPath, apiPath } from "../lib/paths";
|
|
|
7
7
|
import { AuthBridge } from "../components/AuthBridge";
|
|
8
8
|
import { GlobalModal } from "../components/ui/GlobalModal";
|
|
9
9
|
import { Toaster } from "../components/ui/Toaster";
|
|
10
|
-
import { getSiteSettings } from "../lib/globals";
|
|
10
|
+
import { getSiteSettings, getGlobal } from "../lib/globals";
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
title: string;
|
|
@@ -15,8 +15,25 @@ interface Props {
|
|
|
15
15
|
|
|
16
16
|
const { title } = Astro.props;
|
|
17
17
|
const siteSettings = await getSiteSettings({ request: Astro.request });
|
|
18
|
+
const seoSettings = await getGlobal("seo-settings", { request: Astro.request });
|
|
19
|
+
|
|
18
20
|
const siteName = siteSettings?.siteName || "Kyro CMS";
|
|
19
21
|
const siteFavicon = siteSettings?.siteFavicon;
|
|
22
|
+
const siteDescription = siteSettings?.siteDescription || "";
|
|
23
|
+
|
|
24
|
+
// SEO Logic
|
|
25
|
+
const seoMode = seoSettings?.seoMode || "simple";
|
|
26
|
+
const includeSiteName = seoSettings?.siteNameInTitle ?? true;
|
|
27
|
+
const titleSeparator = seoSettings?.separator || " - ";
|
|
28
|
+
const defaultTitle = seoSettings?.defaultTitle || title;
|
|
29
|
+
const defaultDescription = seoSettings?.defaultDescription || siteDescription;
|
|
30
|
+
const canonicalUrl = seoMode === "advanced" ? seoSettings?.meta?.canonicalUrl : "";
|
|
31
|
+
const robots = seoMode === "advanced" && seoSettings?.meta?.robots ? seoSettings.meta.robots : "noindex, nofollow";
|
|
32
|
+
|
|
33
|
+
let displayTitle = title === "Dashboard" && defaultTitle !== "Dashboard" ? defaultTitle : title;
|
|
34
|
+
if (includeSiteName) {
|
|
35
|
+
displayTitle = `${displayTitle}${titleSeparator}${siteName}`;
|
|
36
|
+
}
|
|
20
37
|
---
|
|
21
38
|
|
|
22
39
|
<!doctype html>
|
|
@@ -24,7 +41,10 @@ const siteFavicon = siteSettings?.siteFavicon;
|
|
|
24
41
|
<head>
|
|
25
42
|
<meta charset="UTF-8" />
|
|
26
43
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
27
|
-
<title>{
|
|
44
|
+
<title>{displayTitle}</title>
|
|
45
|
+
{defaultDescription && <meta name="description" content={defaultDescription} />}
|
|
46
|
+
{robots && <meta name="robots" content={robots} />}
|
|
47
|
+
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
|
28
48
|
<link
|
|
29
49
|
rel="icon"
|
|
30
50
|
type={siteFavicon?.mimeType || "image/svg+xml"}
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
import "../styles/main.css";
|
|
3
3
|
import { adminPath, apiPath } from "../lib/paths";
|
|
4
|
+
import { getSiteSettings, getGlobal } from "../lib/globals";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
title: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
const { title } = Astro.props;
|
|
11
|
+
const siteSettings = await getSiteSettings({ request: Astro.request });
|
|
12
|
+
const seoSettings = await getGlobal("seo-settings", { request: Astro.request });
|
|
13
|
+
|
|
14
|
+
const siteName = siteSettings?.siteName || "Kyro CMS";
|
|
15
|
+
const siteFavicon = siteSettings?.siteFavicon;
|
|
16
|
+
const siteLogo = siteSettings?.siteLogo;
|
|
17
|
+
const logoWidth = siteSettings?.logo?.width;
|
|
18
|
+
const logoHeight = siteSettings?.logo?.height;
|
|
19
|
+
const logoAlt = siteSettings?.logo?.altText || siteName;
|
|
20
|
+
const siteDescription = siteSettings?.siteDescription || "";
|
|
21
|
+
|
|
22
|
+
// SEO Logic
|
|
23
|
+
const seoMode = seoSettings?.seoMode || "simple";
|
|
24
|
+
const includeSiteName = seoSettings?.siteNameInTitle ?? true;
|
|
25
|
+
const titleSeparator = seoSettings?.separator || " - ";
|
|
26
|
+
const defaultTitle = seoSettings?.defaultTitle || title;
|
|
27
|
+
const defaultDescription = seoSettings?.defaultDescription || siteDescription;
|
|
28
|
+
const canonicalUrl = seoMode === "advanced" ? seoSettings?.meta?.canonicalUrl : "";
|
|
29
|
+
const robots = seoMode === "advanced" && seoSettings?.meta?.robots ? seoSettings.meta.robots : "noindex, nofollow";
|
|
30
|
+
|
|
31
|
+
let displayTitle = title === "Login" && defaultTitle !== "Login" ? defaultTitle : title;
|
|
32
|
+
if (includeSiteName) {
|
|
33
|
+
displayTitle = `${displayTitle}${titleSeparator}${siteName}`;
|
|
34
|
+
}
|
|
10
35
|
---
|
|
11
36
|
|
|
12
37
|
<!doctype html>
|
|
@@ -14,8 +39,15 @@ const { title } = Astro.props;
|
|
|
14
39
|
<head>
|
|
15
40
|
<meta charset="UTF-8" />
|
|
16
41
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
17
|
-
<title>{
|
|
18
|
-
<
|
|
42
|
+
<title>{displayTitle}</title>
|
|
43
|
+
{defaultDescription && <meta name="description" content={defaultDescription} />}
|
|
44
|
+
{robots && <meta name="robots" content={robots} />}
|
|
45
|
+
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
|
46
|
+
<link
|
|
47
|
+
rel="icon"
|
|
48
|
+
type={siteFavicon?.mimeType || "image/svg+xml"}
|
|
49
|
+
href={siteFavicon?.url || "/favicon.svg"}
|
|
50
|
+
/>
|
|
19
51
|
<script is:inline define:vars={{ adminPath, apiPath }}>
|
|
20
52
|
(async () => {
|
|
21
53
|
try {
|
|
@@ -49,11 +81,39 @@ const { title } = Astro.props;
|
|
|
49
81
|
<div class="w-full max-w-md flex flex-col items-center">
|
|
50
82
|
<!-- Logo -->
|
|
51
83
|
<div class="w-full text-center mb-8">
|
|
52
|
-
<a href="/" class="inline-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
<a href="/" class="inline-flex items-center justify-center gap-2">
|
|
85
|
+
{
|
|
86
|
+
siteLogo ? (
|
|
87
|
+
<img
|
|
88
|
+
src={siteLogo.url}
|
|
89
|
+
alt={logoAlt}
|
|
90
|
+
style={{
|
|
91
|
+
width: logoWidth ? `${logoWidth}px` : "auto",
|
|
92
|
+
height: logoHeight ? `${logoHeight}px` : "40px",
|
|
93
|
+
objectFit: "contain",
|
|
94
|
+
}}
|
|
95
|
+
class="rounded-lg"
|
|
96
|
+
/>
|
|
97
|
+
) : (
|
|
98
|
+
<>
|
|
99
|
+
<svg class="w-7 h-7 text-[var(--kyro-text-primary)]" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
100
|
+
<defs>
|
|
101
|
+
<mask id="kyro-cutout-auth">
|
|
102
|
+
<rect width="100" height="100" fill="white" />
|
|
103
|
+
<path d="M 40 89.72 L 40 40 L 89.72 40 A 45 45 0 0 1 40 89.72 Z" stroke="black" stroke-width="8" fill="none" />
|
|
104
|
+
</mask>
|
|
105
|
+
</defs>
|
|
106
|
+
<g mask="url(#kyro-cutout-auth)" fill="currentColor">
|
|
107
|
+
<circle cx="45" cy="45" r="45" />
|
|
108
|
+
<rect x="40" y="40" width="60" height="60" />
|
|
109
|
+
</g>
|
|
110
|
+
</svg>
|
|
111
|
+
<span class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
|
|
112
|
+
{siteName}
|
|
113
|
+
</span>
|
|
114
|
+
</>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
57
117
|
</a>
|
|
58
118
|
</div>
|
|
59
119
|
|