@sonicjs-cms/core 2.3.12 → 2.3.14
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/{chunk-REY542YK.js → chunk-AVPUX57O.js} +3 -3
- package/dist/{chunk-REY542YK.js.map → chunk-AVPUX57O.js.map} +1 -1
- package/dist/{chunk-RIOIKM3Y.cjs → chunk-AZLU3ROK.cjs} +4 -2
- package/dist/chunk-AZLU3ROK.cjs.map +1 -0
- package/dist/{chunk-NTXPL746.js → chunk-CAJOP354.js} +34 -2
- package/dist/chunk-CAJOP354.js.map +1 -0
- package/dist/{chunk-HTJLBF6F.cjs → chunk-D4PJFFOV.cjs} +652 -475
- package/dist/chunk-D4PJFFOV.cjs.map +1 -0
- package/dist/{chunk-P6NMVNJJ.cjs → chunk-ETS5XSAG.cjs} +34 -2
- package/dist/chunk-ETS5XSAG.cjs.map +1 -0
- package/dist/{chunk-EIE35JCC.js → chunk-H34L445M.js} +3 -3
- package/dist/{chunk-EIE35JCC.js.map → chunk-H34L445M.js.map} +1 -1
- package/dist/{chunk-74RYBO6J.js → chunk-SKPETEM5.js} +10 -5
- package/dist/chunk-SKPETEM5.js.map +1 -0
- package/dist/{chunk-IB6UBZVD.cjs → chunk-SZE3XVET.cjs} +10 -5
- package/dist/chunk-SZE3XVET.cjs.map +1 -0
- package/dist/{chunk-HDSRB23N.js → chunk-T4XRPNX2.js} +507 -330
- package/dist/chunk-T4XRPNX2.js.map +1 -0
- package/dist/{chunk-KQCYQKSV.js → chunk-V5LBQN3I.js} +4 -2
- package/dist/chunk-V5LBQN3I.js.map +1 -0
- package/dist/{chunk-OJ5WUCSH.cjs → chunk-XWPGIFS7.cjs} +4 -4
- package/dist/{chunk-OJ5WUCSH.cjs.map → chunk-XWPGIFS7.cjs.map} +1 -1
- package/dist/{chunk-K6BFUYJH.cjs → chunk-YIXSSJWD.cjs} +5 -5
- package/dist/{chunk-K6BFUYJH.cjs.map → chunk-YIXSSJWD.cjs.map} +1 -1
- package/dist/index.cjs +1080 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1003 -10
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +23 -23
- package/dist/middleware.js +2 -2
- package/dist/migrations-3A53GREK.cjs +13 -0
- package/dist/{migrations-DQ74P6V4.cjs.map → migrations-3A53GREK.cjs.map} +1 -1
- package/dist/migrations-WF6VIVU2.js +4 -0
- package/dist/{migrations-YAFC5JVO.js.map → migrations-WF6VIVU2.js.map} +1 -1
- package/dist/routes.cjs +25 -25
- package/dist/routes.js +5 -5
- package/dist/services.cjs +2 -2
- package/dist/services.js +1 -1
- package/dist/templates.cjs +17 -17
- package/dist/templates.js +2 -2
- package/dist/utils.cjs +11 -11
- package/dist/utils.js +1 -1
- package/migrations/025_add_easymde_plugin.sql +25 -0
- package/package.json +8 -3
- package/dist/chunk-74RYBO6J.js.map +0 -1
- package/dist/chunk-HDSRB23N.js.map +0 -1
- package/dist/chunk-HTJLBF6F.cjs.map +0 -1
- package/dist/chunk-IB6UBZVD.cjs.map +0 -1
- package/dist/chunk-KQCYQKSV.js.map +0 -1
- package/dist/chunk-NTXPL746.js.map +0 -1
- package/dist/chunk-P6NMVNJJ.cjs.map +0 -1
- package/dist/chunk-RIOIKM3Y.cjs.map +0 -1
- package/dist/migrations-DQ74P6V4.cjs +0 -13
- package/dist/migrations-YAFC5JVO.js +0 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-3YNNVSMC.js';
|
|
2
|
-
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-
|
|
2
|
+
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-H34L445M.js';
|
|
3
3
|
import { PluginService } from './chunk-SGAG6FD3.js';
|
|
4
|
-
import { MigrationService } from './chunk-
|
|
5
|
-
import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-
|
|
6
|
-
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-
|
|
4
|
+
import { MigrationService } from './chunk-CAJOP354.js';
|
|
5
|
+
import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-V5LBQN3I.js';
|
|
6
|
+
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-SKPETEM5.js';
|
|
7
7
|
import { metricsTracker } from './chunk-FICTAGD4.js';
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
import { cors } from 'hono/cors';
|
|
@@ -1569,7 +1569,7 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1569
1569
|
}
|
|
1570
1570
|
const validatedData = validation.data;
|
|
1571
1571
|
const db = c.env.DB;
|
|
1572
|
-
const
|
|
1572
|
+
const _user = c.get("user");
|
|
1573
1573
|
const displayName = validatedData.displayName || validatedData.display_name || "";
|
|
1574
1574
|
const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
|
|
1575
1575
|
const existing = await existingStmt.bind(validatedData.name).first();
|
|
@@ -1720,7 +1720,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1720
1720
|
});
|
|
1721
1721
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
1722
1722
|
try {
|
|
1723
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1723
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-WF6VIVU2.js');
|
|
1724
1724
|
const db = c.env.DB;
|
|
1725
1725
|
const migrationService = new MigrationService2(db);
|
|
1726
1726
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -1745,7 +1745,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1745
1745
|
error: "Unauthorized. Admin access required."
|
|
1746
1746
|
}, 403);
|
|
1747
1747
|
}
|
|
1748
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1748
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-WF6VIVU2.js');
|
|
1749
1749
|
const db = c.env.DB;
|
|
1750
1750
|
const migrationService = new MigrationService2(db);
|
|
1751
1751
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -1764,7 +1764,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1764
1764
|
});
|
|
1765
1765
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
1766
1766
|
try {
|
|
1767
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1767
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-WF6VIVU2.js');
|
|
1768
1768
|
const db = c.env.DB;
|
|
1769
1769
|
const migrationService = new MigrationService2(db);
|
|
1770
1770
|
const validation = await migrationService.validateSchema();
|
|
@@ -2117,6 +2117,27 @@ function renderRegisterPage(data) {
|
|
|
2117
2117
|
</html>
|
|
2118
2118
|
`;
|
|
2119
2119
|
}
|
|
2120
|
+
async function isRegistrationEnabled(db) {
|
|
2121
|
+
try {
|
|
2122
|
+
const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
|
|
2123
|
+
if (plugin?.settings) {
|
|
2124
|
+
const settings = JSON.parse(plugin.settings);
|
|
2125
|
+
const enabled = settings?.registration?.enabled;
|
|
2126
|
+
return enabled !== false && enabled !== 0;
|
|
2127
|
+
}
|
|
2128
|
+
return true;
|
|
2129
|
+
} catch {
|
|
2130
|
+
return true;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
async function isFirstUserRegistration(db) {
|
|
2134
|
+
try {
|
|
2135
|
+
const result = await db.prepare("SELECT COUNT(*) as count FROM users").first();
|
|
2136
|
+
return result?.count === 0;
|
|
2137
|
+
} catch {
|
|
2138
|
+
return false;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2120
2141
|
var baseRegistrationSchema = z.object({
|
|
2121
2142
|
email: z.string().email("Valid email is required"),
|
|
2122
2143
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
@@ -2168,7 +2189,15 @@ authRoutes.get("/login", async (c) => {
|
|
|
2168
2189
|
}
|
|
2169
2190
|
return c.html(renderLoginPage(pageData, demoLoginActive));
|
|
2170
2191
|
});
|
|
2171
|
-
authRoutes.get("/register", (c) => {
|
|
2192
|
+
authRoutes.get("/register", async (c) => {
|
|
2193
|
+
const db = c.env.DB;
|
|
2194
|
+
const isFirstUser = await isFirstUserRegistration(db);
|
|
2195
|
+
if (!isFirstUser) {
|
|
2196
|
+
const registrationEnabled = await isRegistrationEnabled(db);
|
|
2197
|
+
if (!registrationEnabled) {
|
|
2198
|
+
return c.redirect("/auth/login?error=Registration is currently disabled");
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2172
2201
|
const error = c.req.query("error");
|
|
2173
2202
|
const pageData = {
|
|
2174
2203
|
error: error || void 0
|
|
@@ -2184,6 +2213,13 @@ authRoutes.post(
|
|
|
2184
2213
|
async (c) => {
|
|
2185
2214
|
try {
|
|
2186
2215
|
const db = c.env.DB;
|
|
2216
|
+
const isFirstUser = await isFirstUserRegistration(db);
|
|
2217
|
+
if (!isFirstUser) {
|
|
2218
|
+
const registrationEnabled = await isRegistrationEnabled(db);
|
|
2219
|
+
if (!registrationEnabled) {
|
|
2220
|
+
return c.json({ error: "Registration is currently disabled" }, 403);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2187
2223
|
let requestData;
|
|
2188
2224
|
try {
|
|
2189
2225
|
requestData = await c.req.json();
|
|
@@ -2376,6 +2412,17 @@ authRoutes.post("/refresh", requireAuth(), async (c) => {
|
|
|
2376
2412
|
authRoutes.post("/register/form", async (c) => {
|
|
2377
2413
|
try {
|
|
2378
2414
|
const db = c.env.DB;
|
|
2415
|
+
const isFirstUser = await isFirstUserRegistration(db);
|
|
2416
|
+
if (!isFirstUser) {
|
|
2417
|
+
const registrationEnabled = await isRegistrationEnabled(db);
|
|
2418
|
+
if (!registrationEnabled) {
|
|
2419
|
+
return c.html(html`
|
|
2420
|
+
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
2421
|
+
Registration is currently disabled. Please contact an administrator.
|
|
2422
|
+
</div>
|
|
2423
|
+
`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2379
2426
|
const formData = await c.req.formData();
|
|
2380
2427
|
const requestData = {
|
|
2381
2428
|
email: formData.get("email"),
|
|
@@ -2409,6 +2456,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2409
2456
|
`);
|
|
2410
2457
|
}
|
|
2411
2458
|
const passwordHash = await AuthManager.hashPassword(password);
|
|
2459
|
+
const role = isFirstUser ? "admin" : "viewer";
|
|
2412
2460
|
const userId = crypto.randomUUID();
|
|
2413
2461
|
const now = /* @__PURE__ */ new Date();
|
|
2414
2462
|
await db.prepare(`
|
|
@@ -2421,14 +2469,13 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2421
2469
|
firstName,
|
|
2422
2470
|
lastName,
|
|
2423
2471
|
passwordHash,
|
|
2424
|
-
|
|
2425
|
-
// First user gets admin role
|
|
2472
|
+
role,
|
|
2426
2473
|
1,
|
|
2427
2474
|
// is_active
|
|
2428
2475
|
now.getTime(),
|
|
2429
2476
|
now.getTime()
|
|
2430
2477
|
).run();
|
|
2431
|
-
const token = await AuthManager.generateToken(userId, normalizedEmail,
|
|
2478
|
+
const token = await AuthManager.generateToken(userId, normalizedEmail, role);
|
|
2432
2479
|
setCookie(c, "auth_token", token, {
|
|
2433
2480
|
httpOnly: true,
|
|
2434
2481
|
secure: false,
|
|
@@ -2437,12 +2484,13 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2437
2484
|
maxAge: 60 * 60 * 24
|
|
2438
2485
|
// 24 hours
|
|
2439
2486
|
});
|
|
2487
|
+
const redirectUrl = role === "admin" ? "/admin/dashboard" : "/admin/dashboard";
|
|
2440
2488
|
return c.html(html`
|
|
2441
2489
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
|
|
2442
|
-
Account created successfully! Redirecting
|
|
2490
|
+
Account created successfully! Redirecting...
|
|
2443
2491
|
<script>
|
|
2444
2492
|
setTimeout(() => {
|
|
2445
|
-
window.location.href = '
|
|
2493
|
+
window.location.href = '${redirectUrl}';
|
|
2446
2494
|
}, 2000);
|
|
2447
2495
|
</script>
|
|
2448
2496
|
</div>
|
|
@@ -4426,6 +4474,13 @@ function getMDXEditorInitScript(config) {
|
|
|
4426
4474
|
// Store reference to editor instance
|
|
4427
4475
|
textarea.easyMDEInstance = easyMDE;
|
|
4428
4476
|
|
|
4477
|
+
// Sync changes back to textarea
|
|
4478
|
+
easyMDE.codemirror.on("change", () => {
|
|
4479
|
+
textarea.value = easyMDE.value();
|
|
4480
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4481
|
+
textarea.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4482
|
+
});
|
|
4483
|
+
|
|
4429
4484
|
console.log('EasyMDE initialized for field:', textarea.id || textarea.name);
|
|
4430
4485
|
} catch (error) {
|
|
4431
4486
|
console.error('Error initializing EasyMDE:', error);
|
|
@@ -6455,10 +6510,9 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6455
6510
|
const insertStmt = db.prepare(`
|
|
6456
6511
|
INSERT INTO content (
|
|
6457
6512
|
id, collection_id, slug, title, data, status,
|
|
6458
|
-
|
|
6459
|
-
meta_title, meta_description, author_id, created_by, created_at, updated_at
|
|
6513
|
+
author_id, created_at, updated_at
|
|
6460
6514
|
)
|
|
6461
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
6515
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6462
6516
|
`);
|
|
6463
6517
|
await insertStmt.bind(
|
|
6464
6518
|
contentId,
|
|
@@ -6467,11 +6521,6 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6467
6521
|
data.title || "Untitled",
|
|
6468
6522
|
JSON.stringify(data),
|
|
6469
6523
|
status,
|
|
6470
|
-
scheduledPublishAt ? new Date(scheduledPublishAt).getTime() : null,
|
|
6471
|
-
scheduledUnpublishAt ? new Date(scheduledUnpublishAt).getTime() : null,
|
|
6472
|
-
data.meta_title || null,
|
|
6473
|
-
data.meta_description || null,
|
|
6474
|
-
user?.userId || "unknown",
|
|
6475
6524
|
user?.userId || "unknown",
|
|
6476
6525
|
now,
|
|
6477
6526
|
now
|
|
@@ -11509,7 +11558,7 @@ adminMediaRoutes.get("/", async (c) => {
|
|
|
11509
11558
|
const type = searchParams.get("type") || "all";
|
|
11510
11559
|
const view = searchParams.get("view") || "grid";
|
|
11511
11560
|
const page = parseInt(searchParams.get("page") || "1");
|
|
11512
|
-
const
|
|
11561
|
+
const _cacheBust = searchParams.get("t");
|
|
11513
11562
|
const limit = 24;
|
|
11514
11563
|
const offset = (page - 1) * limit;
|
|
11515
11564
|
const db = c.env.DB;
|
|
@@ -12338,10 +12387,37 @@ function formatFileSize(bytes) {
|
|
|
12338
12387
|
// src/templates/pages/admin-plugins-list.template.ts
|
|
12339
12388
|
init_admin_layout_catalyst_template();
|
|
12340
12389
|
function renderPluginsListPage(data) {
|
|
12390
|
+
const categories = [
|
|
12391
|
+
{ value: "content", label: "Content Management" },
|
|
12392
|
+
{ value: "media", label: "Media" },
|
|
12393
|
+
{ value: "editor", label: "Editors" },
|
|
12394
|
+
{ value: "seo", label: "SEO & Analytics" },
|
|
12395
|
+
{ value: "security", label: "Security" },
|
|
12396
|
+
{ value: "utilities", label: "Utilities" },
|
|
12397
|
+
{ value: "system", label: "System" },
|
|
12398
|
+
{ value: "development", label: "Development" },
|
|
12399
|
+
{ value: "demo", label: "Demo" }
|
|
12400
|
+
];
|
|
12401
|
+
const statuses = [
|
|
12402
|
+
{ value: "active", label: "Active" },
|
|
12403
|
+
{ value: "inactive", label: "Inactive" },
|
|
12404
|
+
{ value: "uninstalled", label: "Available to Install" },
|
|
12405
|
+
{ value: "error", label: "Error" }
|
|
12406
|
+
];
|
|
12407
|
+
const categoryCounts = {};
|
|
12408
|
+
categories.forEach((cat) => {
|
|
12409
|
+
categoryCounts[cat.value] = data.plugins.filter((p) => p.category === cat.value).length;
|
|
12410
|
+
});
|
|
12411
|
+
categories.sort((a, b) => (categoryCounts[b.value] || 0) - (categoryCounts[a.value] || 0));
|
|
12412
|
+
const statusCounts = {};
|
|
12413
|
+
statuses.forEach((status) => {
|
|
12414
|
+
statusCounts[status.value] = data.plugins.filter((p) => p.status === status.value).length;
|
|
12415
|
+
});
|
|
12416
|
+
statuses.sort((a, b) => (statusCounts[b.value] || 0) - (statusCounts[a.value] || 0));
|
|
12341
12417
|
const pageContent = `
|
|
12342
12418
|
<div>
|
|
12343
12419
|
<!-- Header -->
|
|
12344
|
-
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-
|
|
12420
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
|
|
12345
12421
|
<div>
|
|
12346
12422
|
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Plugins</h1>
|
|
12347
12423
|
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Manage and extend functionality with plugins</p>
|
|
@@ -12349,7 +12425,7 @@ function renderPluginsListPage(data) {
|
|
|
12349
12425
|
</div>
|
|
12350
12426
|
|
|
12351
12427
|
<!-- Experimental Notice -->
|
|
12352
|
-
<div class="mb-
|
|
12428
|
+
<div class="mb-8 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 p-4">
|
|
12353
12429
|
<div class="flex items-start">
|
|
12354
12430
|
<div class="flex-shrink-0">
|
|
12355
12431
|
<svg class="h-5 w-5 text-amber-600 dark:text-amber-400" viewBox="0 0 20 20" fill="currentColor">
|
|
@@ -12370,176 +12446,174 @@ function renderPluginsListPage(data) {
|
|
|
12370
12446
|
</div>
|
|
12371
12447
|
</div>
|
|
12372
12448
|
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
<
|
|
12376
|
-
|
|
12377
|
-
<div
|
|
12378
|
-
<
|
|
12379
|
-
<
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
<
|
|
12385
|
-
<
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
</svg>
|
|
12402
|
-
<span class="sr-only">Increased by</span>
|
|
12403
|
-
12.3%
|
|
12404
|
-
</div>
|
|
12405
|
-
</dd>
|
|
12449
|
+
<div class="flex flex-col lg:flex-row gap-8">
|
|
12450
|
+
<!-- Sidebar Filters -->
|
|
12451
|
+
<aside class="w-full lg:w-48 flex-shrink-0 space-y-8 lg:sticky lg:top-6 lg:self-start">
|
|
12452
|
+
<!-- Categories Filter -->
|
|
12453
|
+
<div>
|
|
12454
|
+
<h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Categories</h3>
|
|
12455
|
+
<div class="space-y-3">
|
|
12456
|
+
${categories.map((cat) => {
|
|
12457
|
+
const count = categoryCounts[cat.value] || 0;
|
|
12458
|
+
const isDisabled = count === 0;
|
|
12459
|
+
return `
|
|
12460
|
+
<div class="flex items-center ${isDisabled ? "opacity-50" : ""}">
|
|
12461
|
+
<input
|
|
12462
|
+
id="category-${cat.value}"
|
|
12463
|
+
name="category"
|
|
12464
|
+
value="${cat.value}"
|
|
12465
|
+
type="checkbox"
|
|
12466
|
+
onchange="filterAndSortPlugins()"
|
|
12467
|
+
class="h-4 w-4 rounded border-zinc-300 dark:border-zinc-700 text-zinc-900 focus:ring-zinc-600 dark:bg-zinc-900 disabled:cursor-not-allowed"
|
|
12468
|
+
${isDisabled ? "disabled" : ""}
|
|
12469
|
+
>
|
|
12470
|
+
<label for="category-${cat.value}" class="ml-3 text-sm text-zinc-600 dark:text-zinc-400 select-none ${isDisabled ? "cursor-not-allowed" : ""}">
|
|
12471
|
+
${cat.label} <span class="text-zinc-400 dark:text-zinc-500">(${count})</span>
|
|
12472
|
+
</label>
|
|
12473
|
+
</div>
|
|
12474
|
+
`;
|
|
12475
|
+
}).join("")}
|
|
12476
|
+
</div>
|
|
12406
12477
|
</div>
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12478
|
+
|
|
12479
|
+
<div class="h-px bg-zinc-200 dark:bg-zinc-800 lg:hidden"></div>
|
|
12480
|
+
|
|
12481
|
+
<!-- Status Filter -->
|
|
12482
|
+
<div>
|
|
12483
|
+
<h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Status</h3>
|
|
12484
|
+
<div class="space-y-3">
|
|
12485
|
+
${statuses.map((status) => {
|
|
12486
|
+
const count = statusCounts[status.value] || 0;
|
|
12487
|
+
const isDisabled = count === 0;
|
|
12488
|
+
let colorClass = "";
|
|
12489
|
+
let ringClass = "";
|
|
12490
|
+
let dotClass = "";
|
|
12491
|
+
switch (status.value) {
|
|
12492
|
+
case "active":
|
|
12493
|
+
colorClass = "text-emerald-700 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10";
|
|
12494
|
+
ringClass = "ring-emerald-600/20";
|
|
12495
|
+
dotClass = "bg-emerald-500 dark:bg-emerald-400";
|
|
12496
|
+
break;
|
|
12497
|
+
case "inactive":
|
|
12498
|
+
colorClass = "text-zinc-700 dark:text-zinc-400 bg-zinc-50 dark:bg-zinc-500/10";
|
|
12499
|
+
ringClass = "ring-zinc-600/20";
|
|
12500
|
+
dotClass = "bg-zinc-500 dark:bg-zinc-400";
|
|
12501
|
+
break;
|
|
12502
|
+
case "error":
|
|
12503
|
+
colorClass = "text-red-700 dark:text-red-400 bg-red-50 dark:bg-red-500/10";
|
|
12504
|
+
ringClass = "ring-red-600/20";
|
|
12505
|
+
dotClass = "bg-red-500 dark:bg-red-400";
|
|
12506
|
+
break;
|
|
12507
|
+
case "uninstalled":
|
|
12508
|
+
colorClass = "text-yellow-700 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-500/10";
|
|
12509
|
+
ringClass = "ring-yellow-600/20";
|
|
12510
|
+
dotClass = "bg-yellow-500 dark:bg-yellow-400";
|
|
12511
|
+
break;
|
|
12512
|
+
default:
|
|
12513
|
+
colorClass = "text-zinc-700 dark:text-zinc-400 bg-zinc-50 dark:bg-zinc-500/10";
|
|
12514
|
+
ringClass = "ring-zinc-600/20";
|
|
12515
|
+
dotClass = "bg-zinc-500 dark:bg-zinc-400";
|
|
12516
|
+
}
|
|
12517
|
+
return `
|
|
12518
|
+
<div class="flex items-center ${isDisabled ? "opacity-50" : ""}">
|
|
12519
|
+
<input
|
|
12520
|
+
id="status-${status.value}"
|
|
12521
|
+
name="status"
|
|
12522
|
+
value="${status.value}"
|
|
12523
|
+
type="checkbox"
|
|
12524
|
+
onchange="filterAndSortPlugins()"
|
|
12525
|
+
class="h-4 w-4 rounded border-zinc-300 dark:border-zinc-700 text-zinc-900 focus:ring-zinc-600 dark:bg-zinc-900 disabled:cursor-not-allowed"
|
|
12526
|
+
${isDisabled ? "disabled" : ""}
|
|
12527
|
+
>
|
|
12528
|
+
<label for="status-${status.value}" class="ml-3 cursor-pointer select-none flex items-center ${isDisabled ? "cursor-not-allowed" : ""}">
|
|
12529
|
+
<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset ${colorClass} ${ringClass}">
|
|
12530
|
+
<span class="mr-1.5 h-1.5 w-1.5 rounded-full ${dotClass}"></span>
|
|
12531
|
+
${status.label}
|
|
12532
|
+
</span>
|
|
12533
|
+
<span class="ml-2 text-xs text-zinc-500 dark:text-zinc-400">(${count})</span>
|
|
12534
|
+
</label>
|
|
12535
|
+
</div>
|
|
12536
|
+
`;
|
|
12537
|
+
}).join("")}
|
|
12538
|
+
</div>
|
|
12421
12539
|
</div>
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
</div>
|
|
12435
|
-
</
|
|
12540
|
+
</aside>
|
|
12541
|
+
|
|
12542
|
+
<!-- Main Content -->
|
|
12543
|
+
<div class="flex-1 min-w-0">
|
|
12544
|
+
<!-- Stats Row (Compact) -->
|
|
12545
|
+
<div class="flex flex-wrap gap-4 mb-6">
|
|
12546
|
+
<div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
|
|
12547
|
+
<div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Total</div>
|
|
12548
|
+
<div class="mt-1 text-lg font-semibold text-zinc-900 dark:text-white">${data.stats?.total || 0}</div>
|
|
12549
|
+
</div>
|
|
12550
|
+
<div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
|
|
12551
|
+
<div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Active</div>
|
|
12552
|
+
<div class="mt-1 text-lg font-semibold text-emerald-600 dark:text-emerald-400">${data.stats?.active || 0}</div>
|
|
12553
|
+
</div>
|
|
12554
|
+
<div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
|
|
12555
|
+
<div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Available</div>
|
|
12556
|
+
<div class="mt-1 text-lg font-semibold text-zinc-600 dark:text-zinc-400">${data.stats?.uninstalled || 0}</div>
|
|
12557
|
+
</div>
|
|
12558
|
+
<div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
|
|
12559
|
+
<div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Errors</div>
|
|
12560
|
+
<div class="mt-1 text-lg font-semibold text-red-600 dark:text-red-400">${data.stats?.errors || 0}</div>
|
|
12561
|
+
</div>
|
|
12436
12562
|
</div>
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
<svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
|
|
12445
|
-
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
|
|
12563
|
+
|
|
12564
|
+
<!-- Toolbar -->
|
|
12565
|
+
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-6">
|
|
12566
|
+
<div class="relative flex-1 w-full">
|
|
12567
|
+
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
12568
|
+
<svg class="h-4 w-4 text-zinc-400" viewBox="0 0 20 20" fill="currentColor">
|
|
12569
|
+
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
|
|
12446
12570
|
</svg>
|
|
12447
|
-
<span class="sr-only">Available</span>
|
|
12448
|
-
Ready
|
|
12449
12571
|
</div>
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12572
|
+
<input
|
|
12573
|
+
id="search-input"
|
|
12574
|
+
type="text"
|
|
12575
|
+
placeholder="Search plugins..."
|
|
12576
|
+
oninput="filterAndSortPlugins()"
|
|
12577
|
+
class="block w-full h-9 rounded-md border-0 py-1.5 pl-10 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700 dark:focus:ring-zinc-500 sm:text-sm sm:leading-6"
|
|
12578
|
+
>
|
|
12579
|
+
</div>
|
|
12454
12580
|
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12581
|
+
<div class="flex items-center gap-3 w-full sm:w-auto">
|
|
12582
|
+
<select id="sort-filter" onchange="filterAndSortPlugins()" class="block w-full sm:w-auto h-9 rounded-md border-0 py-1.5 pl-3 pr-8 text-zinc-900 ring-1 ring-inset ring-zinc-300 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700 dark:focus:ring-zinc-500 sm:text-sm sm:leading-6">
|
|
12583
|
+
<option value="name-asc">Name (A-Z)</option>
|
|
12584
|
+
<option value="name-desc">Name (Z-A)</option>
|
|
12585
|
+
<option value="newest">Newest Installed</option>
|
|
12586
|
+
<option value="updated">Recently Updated</option>
|
|
12587
|
+
<option value="popular">Popularity</option>
|
|
12588
|
+
<option value="rating">Highest Rated</option>
|
|
12589
|
+
</select>
|
|
12459
12590
|
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
<
|
|
12465
|
-
<
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
<option value="">All Categories</option>
|
|
12469
|
-
<option value="content">Content Management</option>
|
|
12470
|
-
<option value="media">Media</option>
|
|
12471
|
-
<option value="seo">SEO & Analytics</option>
|
|
12472
|
-
<option value="security">Security</option>
|
|
12473
|
-
<option value="utilities">Utilities</option>
|
|
12474
|
-
<option value="system">System</option>
|
|
12475
|
-
<option value="development">Development</option>
|
|
12476
|
-
<option value="demo">Demo</option>
|
|
12477
|
-
</select>
|
|
12478
|
-
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
|
|
12479
|
-
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
12480
|
-
</svg>
|
|
12481
|
-
</div>
|
|
12482
|
-
</div>
|
|
12483
|
-
<div>
|
|
12484
|
-
<label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">Status</label>
|
|
12485
|
-
<div class="mt-2 grid grid-cols-1">
|
|
12486
|
-
<select id="status-filter" onchange="filterPlugins()" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 sm:text-sm/6 min-w-48">
|
|
12487
|
-
<option value="">All Status</option>
|
|
12488
|
-
<option value="active">Active</option>
|
|
12489
|
-
<option value="inactive">Inactive</option>
|
|
12490
|
-
<option value="uninstalled">Available to Install</option>
|
|
12491
|
-
<option value="error">Error</option>
|
|
12492
|
-
</select>
|
|
12493
|
-
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
|
|
12494
|
-
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
12495
|
-
</svg>
|
|
12496
|
-
</div>
|
|
12497
|
-
</div>
|
|
12498
|
-
<div class="flex-1 max-w-md">
|
|
12499
|
-
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search</label>
|
|
12500
|
-
<div class="relative group">
|
|
12501
|
-
<div class="absolute left-3.5 top-2.5 flex items-center justify-center w-5 h-5 rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 dark:from-cyan-300 dark:to-blue-400 opacity-90 group-focus-within:opacity-100 transition-opacity">
|
|
12502
|
-
<svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
|
12503
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
12504
|
-
</svg>
|
|
12505
|
-
</div>
|
|
12506
|
-
<input
|
|
12507
|
-
id="search-input"
|
|
12508
|
-
type="text"
|
|
12509
|
-
placeholder="Search plugins..."
|
|
12510
|
-
oninput="filterPlugins()"
|
|
12511
|
-
class="w-full rounded-full bg-transparent px-11 py-2 text-sm text-zinc-950 dark:text-white placeholder-zinc-500 dark:placeholder-zinc-400 border-2 border-cyan-200/50 dark:border-cyan-700/50 focus:outline-none focus:border-cyan-500 dark:focus:border-cyan-400 focus:shadow-lg focus:shadow-cyan-500/20 dark:focus:shadow-cyan-400/20 transition-all duration-300"
|
|
12512
|
-
/>
|
|
12513
|
-
</div>
|
|
12514
|
-
</div>
|
|
12515
|
-
</div>
|
|
12516
|
-
<div class="flex items-center gap-x-3 ml-4">
|
|
12517
|
-
<button
|
|
12518
|
-
onclick="location.reload()"
|
|
12519
|
-
class="inline-flex items-center gap-x-1.5 px-3 py-1.5 bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm text-zinc-950 dark:text-white text-sm font-medium rounded-full ring-1 ring-inset ring-cyan-200/50 dark:ring-cyan-700/50 hover:bg-gradient-to-r hover:from-cyan-50 hover:to-blue-50 dark:hover:from-cyan-900/30 dark:hover:to-blue-900/30 hover:ring-cyan-300 dark:hover:ring-cyan-600 transition-all duration-200"
|
|
12520
|
-
>
|
|
12521
|
-
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12522
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
12523
|
-
</svg>
|
|
12524
|
-
Refresh
|
|
12525
|
-
</button>
|
|
12526
|
-
</div>
|
|
12591
|
+
<button
|
|
12592
|
+
onclick="location.reload()"
|
|
12593
|
+
class="inline-flex items-center gap-x-1.5 rounded-md bg-white dark:bg-zinc-900 px-3 py-1.5 h-9 text-sm font-semibold text-zinc-900 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800"
|
|
12594
|
+
>
|
|
12595
|
+
<svg class="h-4 w-4 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12596
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
12597
|
+
</svg>
|
|
12598
|
+
</button>
|
|
12527
12599
|
</div>
|
|
12528
12600
|
</div>
|
|
12601
|
+
|
|
12602
|
+
<!-- Plugins Grid -->
|
|
12603
|
+
<div id="plugins-grid" class="grid gap-6" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));">
|
|
12604
|
+
${data.plugins.map((plugin) => renderPluginCard(plugin)).join("")}
|
|
12605
|
+
</div>
|
|
12529
12606
|
</div>
|
|
12530
12607
|
</div>
|
|
12531
|
-
|
|
12532
|
-
<!-- Plugins Grid -->
|
|
12533
|
-
<div id="plugins-grid" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
|
12534
|
-
${data.plugins.map((plugin) => renderPluginCard(plugin)).join("")}
|
|
12535
12608
|
</div>
|
|
12536
12609
|
|
|
12537
12610
|
<script>
|
|
12538
|
-
async function togglePlugin(pluginId, action) {
|
|
12539
|
-
const button = event.target;
|
|
12540
|
-
|
|
12611
|
+
async function togglePlugin(pluginId, action, event) {
|
|
12612
|
+
const button = event.target.closest('button');
|
|
12613
|
+
if (!button) return;
|
|
12614
|
+
|
|
12541
12615
|
button.disabled = true;
|
|
12542
|
-
button.
|
|
12616
|
+
button.classList.add('opacity-50', 'cursor-wait');
|
|
12543
12617
|
|
|
12544
12618
|
try {
|
|
12545
12619
|
const response = await fetch(\`/admin/plugins/\${pluginId}/\${action}\`, {
|
|
@@ -12555,27 +12629,36 @@ function renderPluginsListPage(data) {
|
|
|
12555
12629
|
// Update UI
|
|
12556
12630
|
const card = button.closest('.plugin-card');
|
|
12557
12631
|
const statusBadge = card.querySelector('.status-badge');
|
|
12632
|
+
const knob = button.querySelector('.toggle-knob');
|
|
12558
12633
|
|
|
12559
12634
|
if (action === 'activate') {
|
|
12560
12635
|
// Update status badge
|
|
12561
|
-
statusBadge.className = 'status-badge inline-flex items-center rounded-
|
|
12562
|
-
statusBadge.innerHTML = '<div class="w-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
button.
|
|
12567
|
-
button.onclick = () => togglePlugin(pluginId, 'deactivate');
|
|
12568
|
-
|
|
12636
|
+
statusBadge.className = 'status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 ring-emerald-600/20';
|
|
12637
|
+
statusBadge.innerHTML = '<div class="w-1.5 h-1.5 bg-emerald-500 dark:bg-emerald-400 rounded-full mr-1.5"></div>Active';
|
|
12638
|
+
|
|
12639
|
+
// Update button state to Active
|
|
12640
|
+
button.className = 'bg-emerald-600 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button';
|
|
12641
|
+
button.setAttribute('aria-checked', 'true');
|
|
12642
|
+
button.onclick = (event) => togglePlugin(pluginId, 'deactivate', event);
|
|
12643
|
+
|
|
12644
|
+
// Update knob position
|
|
12645
|
+
if (knob) {
|
|
12646
|
+
knob.className = 'translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob';
|
|
12647
|
+
}
|
|
12569
12648
|
} else {
|
|
12570
12649
|
// Update status badge
|
|
12571
|
-
statusBadge.className = 'status-badge inline-flex items-center rounded-
|
|
12572
|
-
statusBadge.innerHTML = '<div class="w-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
button.
|
|
12577
|
-
button.onclick = () => togglePlugin(pluginId, 'activate');
|
|
12578
|
-
|
|
12650
|
+
statusBadge.className = 'status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-600/20';
|
|
12651
|
+
statusBadge.innerHTML = '<div class="w-1.5 h-1.5 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-1.5"></div>Inactive';
|
|
12652
|
+
|
|
12653
|
+
// Update button state to Inactive
|
|
12654
|
+
button.className = 'bg-zinc-200 dark:bg-zinc-700 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button';
|
|
12655
|
+
button.setAttribute('aria-checked', 'false');
|
|
12656
|
+
button.onclick = (event) => togglePlugin(pluginId, 'activate', event);
|
|
12657
|
+
|
|
12658
|
+
// Update knob position
|
|
12659
|
+
if (knob) {
|
|
12660
|
+
knob.className = 'translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob';
|
|
12661
|
+
}
|
|
12579
12662
|
}
|
|
12580
12663
|
|
|
12581
12664
|
showNotification(\`Plugin \${action}d successfully\`, 'success');
|
|
@@ -12584,9 +12667,9 @@ function renderPluginsListPage(data) {
|
|
|
12584
12667
|
}
|
|
12585
12668
|
} catch (error) {
|
|
12586
12669
|
showNotification(error.message, 'error');
|
|
12587
|
-
button.textContent = originalText;
|
|
12588
12670
|
} finally {
|
|
12589
12671
|
button.disabled = false;
|
|
12672
|
+
button.classList.remove('opacity-50', 'cursor-wait');
|
|
12590
12673
|
}
|
|
12591
12674
|
}
|
|
12592
12675
|
|
|
@@ -12670,81 +12753,92 @@ function renderPluginsListPage(data) {
|
|
|
12670
12753
|
showNotification('Plugin details coming soon!', 'info');
|
|
12671
12754
|
}
|
|
12672
12755
|
|
|
12673
|
-
function
|
|
12674
|
-
|
|
12675
|
-
const
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
}, 3000);
|
|
12683
|
-
}
|
|
12684
|
-
|
|
12685
|
-
function filterPlugins() {
|
|
12686
|
-
const categoryFilter = document.getElementById('category-filter').value.toLowerCase();
|
|
12687
|
-
const statusFilter = document.getElementById('status-filter').value.toLowerCase();
|
|
12756
|
+
function filterAndSortPlugins() {
|
|
12757
|
+
// Get checked categories
|
|
12758
|
+
const checkedCategories = Array.from(document.querySelectorAll('input[name="category"]:checked'))
|
|
12759
|
+
.map(cb => cb.value.toLowerCase());
|
|
12760
|
+
|
|
12761
|
+
// Get checked statuses
|
|
12762
|
+
const checkedStatuses = Array.from(document.querySelectorAll('input[name="status"]:checked'))
|
|
12763
|
+
.map(cb => cb.value.toLowerCase());
|
|
12764
|
+
|
|
12688
12765
|
const searchInput = document.getElementById('search-input').value.toLowerCase();
|
|
12766
|
+
const sortValue = document.getElementById('sort-filter').value;
|
|
12689
12767
|
|
|
12690
|
-
const
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12768
|
+
const pluginsGrid = document.getElementById('plugins-grid');
|
|
12769
|
+
const pluginCards = Array.from(pluginsGrid.querySelectorAll('.plugin-card'));
|
|
12770
|
+
|
|
12771
|
+
// Filter
|
|
12772
|
+
const visibleCards = pluginCards.filter(card => {
|
|
12695
12773
|
const category = card.getAttribute('data-category')?.toLowerCase() || '';
|
|
12696
12774
|
const status = card.getAttribute('data-status')?.toLowerCase() || '';
|
|
12697
12775
|
const name = card.getAttribute('data-name')?.toLowerCase() || '';
|
|
12698
12776
|
const description = card.getAttribute('data-description')?.toLowerCase() || '';
|
|
12699
12777
|
|
|
12700
|
-
//
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
//
|
|
12704
|
-
if (
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
}
|
|
12712
|
-
|
|
12713
|
-
// Search filter - check if search term is in name or description
|
|
12714
|
-
if (searchInput && !name.includes(searchInput) && !description.includes(searchInput)) {
|
|
12715
|
-
matches = false;
|
|
12716
|
-
}
|
|
12778
|
+
// Category filter: if any selected, must match one of them
|
|
12779
|
+
if (checkedCategories.length > 0 && !checkedCategories.includes(category)) return false;
|
|
12780
|
+
|
|
12781
|
+
// Status filter: if any selected, must match one of them
|
|
12782
|
+
if (checkedStatuses.length > 0 && !checkedStatuses.includes(status)) return false;
|
|
12783
|
+
|
|
12784
|
+
// Search filter
|
|
12785
|
+
if (searchInput && !name.includes(searchInput) && !description.includes(searchInput)) return false;
|
|
12786
|
+
|
|
12787
|
+
return true;
|
|
12788
|
+
});
|
|
12717
12789
|
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12790
|
+
// Sort
|
|
12791
|
+
visibleCards.sort((a, b) => {
|
|
12792
|
+
const aName = a.getAttribute('data-name') || '';
|
|
12793
|
+
const bName = b.getAttribute('data-name') || '';
|
|
12794
|
+
const aInstalled = parseInt(a.getAttribute('data-installed') || '0');
|
|
12795
|
+
const bInstalled = parseInt(b.getAttribute('data-installed') || '0');
|
|
12796
|
+
const aUpdated = parseInt(a.getAttribute('data-updated') || '0');
|
|
12797
|
+
const bUpdated = parseInt(b.getAttribute('data-updated') || '0');
|
|
12798
|
+
const aDownloads = parseInt(a.getAttribute('data-downloads') || '0');
|
|
12799
|
+
const bDownloads = parseInt(b.getAttribute('data-downloads') || '0');
|
|
12800
|
+
const aRating = parseFloat(a.getAttribute('data-rating') || '0');
|
|
12801
|
+
const bRating = parseFloat(b.getAttribute('data-rating') || '0');
|
|
12802
|
+
|
|
12803
|
+
switch (sortValue) {
|
|
12804
|
+
case 'name-desc': return bName.localeCompare(aName);
|
|
12805
|
+
case 'newest': return bInstalled - aInstalled;
|
|
12806
|
+
case 'updated': return bUpdated - aUpdated;
|
|
12807
|
+
case 'popular': return bDownloads - aDownloads;
|
|
12808
|
+
case 'rating': return bRating - aRating;
|
|
12809
|
+
case 'name-asc':
|
|
12810
|
+
default: return aName.localeCompare(bName);
|
|
12724
12811
|
}
|
|
12725
12812
|
});
|
|
12726
12813
|
|
|
12727
|
-
//
|
|
12814
|
+
// Re-append
|
|
12815
|
+
pluginCards.forEach(card => card.style.display = 'none'); // Hide all first
|
|
12816
|
+
|
|
12817
|
+
// If no results
|
|
12728
12818
|
let noResultsMsg = document.getElementById('no-results-message');
|
|
12729
|
-
if (
|
|
12819
|
+
if (visibleCards.length === 0) {
|
|
12730
12820
|
if (!noResultsMsg) {
|
|
12731
12821
|
noResultsMsg = document.createElement('div');
|
|
12732
12822
|
noResultsMsg.id = 'no-results-message';
|
|
12733
|
-
noResultsMsg.className = 'col-span-full text-center py-12';
|
|
12823
|
+
noResultsMsg.className = 'col-span-full text-center py-12 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-dashed border-zinc-300 dark:border-zinc-700';
|
|
12734
12824
|
noResultsMsg.innerHTML = \`
|
|
12735
12825
|
<div class="flex flex-col items-center">
|
|
12736
|
-
<svg class="w-
|
|
12826
|
+
<svg class="w-12 h-12 text-zinc-400 dark:text-zinc-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12737
12827
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
12738
12828
|
</svg>
|
|
12739
|
-
<h3 class="text-
|
|
12829
|
+
<h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-1">No plugins found</h3>
|
|
12740
12830
|
<p class="text-sm text-zinc-500 dark:text-zinc-400">Try adjusting your filters or search terms</p>
|
|
12741
12831
|
</div>
|
|
12742
12832
|
\`;
|
|
12743
|
-
|
|
12833
|
+
pluginsGrid.appendChild(noResultsMsg);
|
|
12744
12834
|
}
|
|
12745
12835
|
noResultsMsg.style.display = '';
|
|
12746
|
-
} else
|
|
12747
|
-
noResultsMsg.style.display = 'none';
|
|
12836
|
+
} else {
|
|
12837
|
+
if (noResultsMsg) noResultsMsg.style.display = 'none';
|
|
12838
|
+
visibleCards.forEach(card => {
|
|
12839
|
+
card.style.display = '';
|
|
12840
|
+
pluginsGrid.appendChild(card); // Re-appending moves it to the end, effectively sorting
|
|
12841
|
+
});
|
|
12748
12842
|
}
|
|
12749
12843
|
}
|
|
12750
12844
|
</script>
|
|
@@ -12775,116 +12869,111 @@ function renderPluginsListPage(data) {
|
|
|
12775
12869
|
}
|
|
12776
12870
|
function renderPluginCard(plugin) {
|
|
12777
12871
|
const statusColors = {
|
|
12778
|
-
active: "bg-
|
|
12779
|
-
inactive: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-
|
|
12780
|
-
error: "bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-red-
|
|
12781
|
-
uninstalled: "bg-zinc-
|
|
12872
|
+
active: "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 ring-emerald-600/20",
|
|
12873
|
+
inactive: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-600/20",
|
|
12874
|
+
error: "bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-red-600/20",
|
|
12875
|
+
uninstalled: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-600 dark:text-zinc-500 ring-zinc-600/20"
|
|
12782
12876
|
};
|
|
12783
12877
|
const statusIcons = {
|
|
12784
|
-
active: '<div class="w-
|
|
12785
|
-
inactive: '<div class="w-
|
|
12786
|
-
error: '<div class="w-
|
|
12787
|
-
uninstalled: '<div class="w-
|
|
12788
|
-
};
|
|
12789
|
-
const borderColors = {
|
|
12790
|
-
active: "ring-[3px] ring-lime-500 dark:ring-lime-400",
|
|
12791
|
-
inactive: "ring-[3px] ring-pink-500 dark:ring-pink-400",
|
|
12792
|
-
error: "ring-[3px] ring-red-500 dark:ring-red-400",
|
|
12793
|
-
uninstalled: "ring-[3px] ring-zinc-400 dark:ring-zinc-600"
|
|
12878
|
+
active: '<div class="w-1.5 h-1.5 bg-emerald-500 dark:bg-emerald-400 rounded-full mr-1.5"></div>',
|
|
12879
|
+
inactive: '<div class="w-1.5 h-1.5 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-1.5"></div>',
|
|
12880
|
+
error: '<div class="w-1.5 h-1.5 bg-red-500 dark:bg-red-400 rounded-full mr-1.5"></div>',
|
|
12881
|
+
uninstalled: '<div class="w-1.5 h-1.5 bg-zinc-400 dark:bg-zinc-600 rounded-full mr-1.5"></div>'
|
|
12794
12882
|
};
|
|
12795
12883
|
const criticalCorePlugins = ["core-auth", "core-media"];
|
|
12796
12884
|
const canToggle = !criticalCorePlugins.includes(plugin.id);
|
|
12797
12885
|
let actionButton = "";
|
|
12798
12886
|
if (plugin.status === "uninstalled") {
|
|
12799
|
-
actionButton = `<button onclick="installPlugin('${plugin.name}')" class="bg-
|
|
12800
|
-
} else if (plugin.status === "active") {
|
|
12801
|
-
actionButton = `<button onclick="togglePlugin('${plugin.id}', 'deactivate')" class="bg-red-600 dark:bg-red-700 hover:bg-red-700 dark:hover:bg-red-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">Deactivate</button>`;
|
|
12887
|
+
actionButton = `<button onclick="installPlugin('${plugin.name}')" class="w-full sm:w-auto bg-zinc-900 dark:bg-white hover:bg-zinc-800 dark:hover:bg-zinc-100 text-white dark:text-zinc-900 px-3 py-1.5 rounded-md text-xs font-medium transition-colors shadow-sm">Install</button>`;
|
|
12802
12888
|
} else {
|
|
12803
|
-
|
|
12889
|
+
const isActive = plugin.status === "active";
|
|
12890
|
+
const action = isActive ? "deactivate" : "activate";
|
|
12891
|
+
const bgClass = isActive ? "bg-emerald-600" : "bg-zinc-200 dark:bg-zinc-700";
|
|
12892
|
+
const translateClass = isActive ? "translate-x-5" : "translate-x-0";
|
|
12893
|
+
if (canToggle) {
|
|
12894
|
+
actionButton = `
|
|
12895
|
+
<button onclick="togglePlugin('${plugin.id}', '${action}', event)" type="button" class="${bgClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button" role="switch" aria-checked="${isActive}">
|
|
12896
|
+
<span class="sr-only">Toggle plugin</span>
|
|
12897
|
+
<span aria-hidden="true" class="${translateClass} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob"></span>
|
|
12898
|
+
</button>
|
|
12899
|
+
`;
|
|
12900
|
+
} else {
|
|
12901
|
+
actionButton = `
|
|
12902
|
+
<div class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-not-allowed rounded-full border-2 border-transparent bg-emerald-600/50 opacity-50" title="Core plugin cannot be disabled">
|
|
12903
|
+
<span class="translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0"></span>
|
|
12904
|
+
</div>
|
|
12905
|
+
`;
|
|
12906
|
+
}
|
|
12804
12907
|
}
|
|
12805
12908
|
return `
|
|
12806
|
-
<div class="plugin-card rounded-
|
|
12909
|
+
<div class="plugin-card flex flex-col h-full rounded-md bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/10 dark:ring-white/10 p-5 transition-all hover:shadow-md"
|
|
12910
|
+
data-category="${plugin.category}"
|
|
12911
|
+
data-status="${plugin.status}"
|
|
12912
|
+
data-name="${plugin.displayName}"
|
|
12913
|
+
data-description="${plugin.description}"
|
|
12914
|
+
data-downloads="${plugin.downloadCount || 0}"
|
|
12915
|
+
data-rating="${plugin.rating || 0}">
|
|
12807
12916
|
<div class="flex items-start justify-between mb-4">
|
|
12808
12917
|
<div class="flex items-center gap-3">
|
|
12809
|
-
<div class="w-
|
|
12918
|
+
<div class="w-10 h-10 rounded-md flex items-center justify-center bg-zinc-50 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 ring-1 ring-inset ring-zinc-200 dark:ring-zinc-700/50">
|
|
12810
12919
|
${plugin.icon || getDefaultPluginIcon(plugin.category)}
|
|
12811
12920
|
</div>
|
|
12812
12921
|
<div>
|
|
12813
|
-
<
|
|
12814
|
-
|
|
12922
|
+
<div class="flex items-center gap-2">
|
|
12923
|
+
<h3 class="text-sm font-semibold text-zinc-900 dark:text-white">${plugin.displayName}</h3>
|
|
12924
|
+
<span class="status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset ${statusColors[plugin.status]}">
|
|
12925
|
+
${statusIcons[plugin.status]}${plugin.status.charAt(0).toUpperCase() + plugin.status.slice(1)}
|
|
12926
|
+
</span>
|
|
12927
|
+
</div>
|
|
12928
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400">v${plugin.version} \u2022 ${plugin.author}</p>
|
|
12815
12929
|
</div>
|
|
12816
12930
|
</div>
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12931
|
+
|
|
12932
|
+
<div class="flex items-center gap-1">
|
|
12933
|
+
${plugin.status !== "uninstalled" ? `
|
|
12934
|
+
<button onclick="showPluginDetails('${plugin.id}')" class="text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Plugin Details">
|
|
12935
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12936
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
12937
|
+
</svg>
|
|
12938
|
+
</button>
|
|
12939
|
+
` : ""}
|
|
12940
|
+
|
|
12941
|
+
${!plugin.isCore && plugin.status !== "uninstalled" ? `
|
|
12942
|
+
<button onclick="uninstallPlugin('${plugin.id}')" class="text-zinc-400 hover:text-red-600 dark:text-zinc-500 dark:hover:text-red-400 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Uninstall Plugin">
|
|
12943
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12944
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
12945
|
+
</svg>
|
|
12946
|
+
</button>
|
|
12947
|
+
` : ""}
|
|
12822
12948
|
</div>
|
|
12823
12949
|
</div>
|
|
12824
12950
|
|
|
12825
|
-
<p class="text-zinc-600 dark:text-zinc-
|
|
12951
|
+
<p class="text-zinc-600 dark:text-zinc-400 text-sm mb-4 line-clamp-2 flex-grow">${plugin.description}</p>
|
|
12826
12952
|
|
|
12827
|
-
<div class="flex items-center gap-
|
|
12828
|
-
<span class="flex items-center
|
|
12829
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12830
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/>
|
|
12831
|
-
</svg>
|
|
12953
|
+
<div class="flex flex-wrap items-center gap-2 mb-5">
|
|
12954
|
+
<span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">
|
|
12832
12955
|
${plugin.category}
|
|
12833
12956
|
</span>
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
<
|
|
12838
|
-
|
|
12839
|
-
</
|
|
12840
|
-
|
|
12841
|
-
</span>
|
|
12842
|
-
` : ""}
|
|
12843
|
-
|
|
12844
|
-
${plugin.rating ? `
|
|
12845
|
-
<span class="flex items-center gap-1">
|
|
12846
|
-
<svg class="w-4 h-4 text-yellow-500 dark:text-yellow-400 fill-current" viewBox="0 0 24 24">
|
|
12847
|
-
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
|
12848
|
-
</svg>
|
|
12849
|
-
${plugin.rating}
|
|
12850
|
-
</span>
|
|
12851
|
-
` : ""}
|
|
12852
|
-
|
|
12853
|
-
<span>${plugin.lastUpdated}</span>
|
|
12854
|
-
</div>
|
|
12855
|
-
|
|
12856
|
-
${plugin.dependencies && plugin.dependencies.length > 0 ? `
|
|
12857
|
-
<div class="mb-4">
|
|
12858
|
-
<p class="text-xs text-zinc-500 dark:text-zinc-400 mb-2">Dependencies:</p>
|
|
12859
|
-
<div class="flex flex-wrap gap-1">
|
|
12860
|
-
${plugin.dependencies.map((dep) => `<span class="inline-block bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 text-xs px-2 py-1 rounded">${dep}</span>`).join("")}
|
|
12861
|
-
</div>
|
|
12957
|
+
${plugin.isCore ? '<span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">Core</span>' : ""}
|
|
12958
|
+
|
|
12959
|
+
${plugin.dependencies && plugin.dependencies.map((dep) => `
|
|
12960
|
+
<span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">
|
|
12961
|
+
${dep}
|
|
12962
|
+
</span>
|
|
12963
|
+
`).join("") || ""}
|
|
12862
12964
|
</div>
|
|
12863
|
-
` : ""}
|
|
12864
12965
|
|
|
12865
|
-
<div class="flex items-center justify-between">
|
|
12966
|
+
<div class="flex items-center justify-between pt-4 border-t border-zinc-100 dark:border-zinc-800 mt-auto">
|
|
12866
12967
|
<div class="flex gap-2">
|
|
12867
|
-
${
|
|
12868
|
-
${plugin.status !== "uninstalled" ? `
|
|
12869
|
-
<button onclick="openPluginSettings('${plugin.id}')" class="bg-white dark:bg-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-700 text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">
|
|
12870
|
-
Settings
|
|
12871
|
-
</button>
|
|
12872
|
-
` : ""}
|
|
12968
|
+
${actionButton}
|
|
12873
12969
|
</div>
|
|
12874
12970
|
|
|
12875
12971
|
<div class="flex items-center gap-2">
|
|
12876
12972
|
${plugin.status !== "uninstalled" ? `
|
|
12877
|
-
<button onclick="
|
|
12878
|
-
<svg class="w-
|
|
12879
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="
|
|
12880
|
-
|
|
12881
|
-
</button>
|
|
12882
|
-
` : ""}
|
|
12883
|
-
|
|
12884
|
-
${!plugin.isCore && plugin.status !== "uninstalled" ? `
|
|
12885
|
-
<button onclick="uninstallPlugin('${plugin.id}')" class="text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 p-1.5 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Uninstall Plugin">
|
|
12886
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12887
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
12973
|
+
<button onclick="openPluginSettings('${plugin.id}')" class="text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Settings">
|
|
12974
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12975
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
12976
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
12888
12977
|
</svg>
|
|
12889
12978
|
</button>
|
|
12890
12979
|
` : ""}
|
|
@@ -18957,6 +19046,8 @@ function renderCollectionFormPage(data) {
|
|
|
18957
19046
|
})
|
|
18958
19047
|
.then(data => {
|
|
18959
19048
|
if (data.success) {
|
|
19049
|
+
// Close modal before reloading
|
|
19050
|
+
closeFieldModal();
|
|
18960
19051
|
location.reload();
|
|
18961
19052
|
} else {
|
|
18962
19053
|
alert('Error saving field: ' + (data.error || 'Unknown error'));
|
|
@@ -19507,11 +19598,65 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
19507
19598
|
return c.json({ success: false, error: "Field name must contain only lowercase letters, numbers, and underscores." });
|
|
19508
19599
|
}
|
|
19509
19600
|
const db = c.env.DB;
|
|
19601
|
+
const getCollectionStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
|
|
19602
|
+
const collection = await getCollectionStmt.bind(collectionId).first();
|
|
19603
|
+
if (!collection) {
|
|
19604
|
+
return c.json({ success: false, error: "Collection not found." });
|
|
19605
|
+
}
|
|
19606
|
+
let schema = collection.schema ? typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema : null;
|
|
19607
|
+
if (schema && schema.properties && schema.properties[fieldName]) {
|
|
19608
|
+
return c.json({ success: false, error: "A field with this name already exists." });
|
|
19609
|
+
}
|
|
19510
19610
|
const existingStmt = db.prepare("SELECT id FROM content_fields WHERE collection_id = ? AND field_name = ?");
|
|
19511
19611
|
const existing = await existingStmt.bind(collectionId, fieldName).first();
|
|
19512
19612
|
if (existing) {
|
|
19513
19613
|
return c.json({ success: false, error: "A field with this name already exists." });
|
|
19514
19614
|
}
|
|
19615
|
+
let parsedOptions = {};
|
|
19616
|
+
try {
|
|
19617
|
+
parsedOptions = fieldOptions ? JSON.parse(fieldOptions) : {};
|
|
19618
|
+
} catch (e) {
|
|
19619
|
+
console.error("Error parsing field options:", e);
|
|
19620
|
+
}
|
|
19621
|
+
if (schema) {
|
|
19622
|
+
if (!schema.properties) {
|
|
19623
|
+
schema.properties = {};
|
|
19624
|
+
}
|
|
19625
|
+
if (!schema.required) {
|
|
19626
|
+
schema.required = [];
|
|
19627
|
+
}
|
|
19628
|
+
const fieldConfig = {
|
|
19629
|
+
type: fieldType === "number" ? "number" : fieldType === "boolean" ? "boolean" : "string",
|
|
19630
|
+
title: fieldLabel,
|
|
19631
|
+
searchable: isSearchable,
|
|
19632
|
+
...parsedOptions
|
|
19633
|
+
};
|
|
19634
|
+
if (fieldType === "richtext") {
|
|
19635
|
+
fieldConfig.format = "richtext";
|
|
19636
|
+
} else if (fieldType === "date") {
|
|
19637
|
+
fieldConfig.format = "date-time";
|
|
19638
|
+
} else if (fieldType === "select") {
|
|
19639
|
+
fieldConfig.enum = parsedOptions.options || [];
|
|
19640
|
+
} else if (fieldType === "media") {
|
|
19641
|
+
fieldConfig.format = "media";
|
|
19642
|
+
} else if (fieldType === "quill") {
|
|
19643
|
+
fieldConfig.type = "quill";
|
|
19644
|
+
} else if (fieldType === "mdxeditor") {
|
|
19645
|
+
fieldConfig.type = "mdxeditor";
|
|
19646
|
+
}
|
|
19647
|
+
schema.properties[fieldName] = fieldConfig;
|
|
19648
|
+
if (isRequired && !schema.required.includes(fieldName)) {
|
|
19649
|
+
schema.required.push(fieldName);
|
|
19650
|
+
}
|
|
19651
|
+
const updateSchemaStmt = db.prepare(`
|
|
19652
|
+
UPDATE collections
|
|
19653
|
+
SET schema = ?, updated_at = ?
|
|
19654
|
+
WHERE id = ?
|
|
19655
|
+
`);
|
|
19656
|
+
await updateSchemaStmt.bind(JSON.stringify(schema), Date.now(), collectionId).run();
|
|
19657
|
+
console.log("[Add Field] Added field to schema:", fieldName, fieldConfig);
|
|
19658
|
+
return c.json({ success: true, fieldId: `schema-${fieldName}` });
|
|
19659
|
+
}
|
|
19515
19660
|
const orderStmt = db.prepare("SELECT MAX(field_order) as max_order FROM content_fields WHERE collection_id = ?");
|
|
19516
19661
|
const orderResult = await orderStmt.bind(collectionId).first();
|
|
19517
19662
|
const nextOrder = (orderResult?.max_order || 0) + 1;
|
|
@@ -19652,7 +19797,39 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19652
19797
|
adminCollectionsRoutes.delete("/:collectionId/fields/:fieldId", async (c) => {
|
|
19653
19798
|
try {
|
|
19654
19799
|
const fieldId = c.req.param("fieldId");
|
|
19800
|
+
const collectionId = c.req.param("collectionId");
|
|
19655
19801
|
const db = c.env.DB;
|
|
19802
|
+
if (fieldId.startsWith("schema-")) {
|
|
19803
|
+
const fieldName = fieldId.replace("schema-", "");
|
|
19804
|
+
const getCollectionStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
|
|
19805
|
+
const collection = await getCollectionStmt.bind(collectionId).first();
|
|
19806
|
+
if (!collection) {
|
|
19807
|
+
return c.json({ success: false, error: "Collection not found." });
|
|
19808
|
+
}
|
|
19809
|
+
let schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
|
|
19810
|
+
if (!schema || !schema.properties) {
|
|
19811
|
+
return c.json({ success: false, error: "Field not found in schema." });
|
|
19812
|
+
}
|
|
19813
|
+
if (schema.properties[fieldName]) {
|
|
19814
|
+
delete schema.properties[fieldName];
|
|
19815
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
19816
|
+
const requiredIndex = schema.required.indexOf(fieldName);
|
|
19817
|
+
if (requiredIndex !== -1) {
|
|
19818
|
+
schema.required.splice(requiredIndex, 1);
|
|
19819
|
+
}
|
|
19820
|
+
}
|
|
19821
|
+
const updateCollectionStmt = db.prepare(`
|
|
19822
|
+
UPDATE collections
|
|
19823
|
+
SET schema = ?, updated_at = ?
|
|
19824
|
+
WHERE id = ?
|
|
19825
|
+
`);
|
|
19826
|
+
await updateCollectionStmt.bind(JSON.stringify(schema), Date.now(), collectionId).run();
|
|
19827
|
+
console.log("[Delete Field] Removed field from schema:", fieldName);
|
|
19828
|
+
return c.json({ success: true });
|
|
19829
|
+
} else {
|
|
19830
|
+
return c.json({ success: false, error: "Field not found in schema." });
|
|
19831
|
+
}
|
|
19832
|
+
}
|
|
19656
19833
|
const deleteStmt = db.prepare("DELETE FROM content_fields WHERE id = ?");
|
|
19657
19834
|
await deleteStmt.bind(fieldId).run();
|
|
19658
19835
|
return c.json({ success: true });
|
|
@@ -21580,5 +21757,5 @@ var ROUTES_INFO = {
|
|
|
21580
21757
|
};
|
|
21581
21758
|
|
|
21582
21759
|
export { PluginBuilder, ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, test_cleanup_default, userRoutes };
|
|
21583
|
-
//# sourceMappingURL=chunk-
|
|
21584
|
-
//# sourceMappingURL=chunk-
|
|
21760
|
+
//# sourceMappingURL=chunk-T4XRPNX2.js.map
|
|
21761
|
+
//# sourceMappingURL=chunk-T4XRPNX2.js.map
|