appflare 0.2.25 → 0.2.27
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/Documentation.md +758 -758
- package/cli/commands/index.ts +238 -238
- package/cli/generate.ts +178 -178
- package/cli/index.ts +120 -120
- package/cli/load-config.ts +184 -184
- package/cli/schema-compiler.ts +1183 -1183
- package/cli/templates/auth/README.md +156 -156
- package/cli/templates/auth/config.ts +61 -61
- package/cli/templates/auth/route-config.ts +1 -1
- package/cli/templates/auth/route-handler.ts +1 -1
- package/cli/templates/auth/route-request-utils.ts +5 -5
- package/cli/templates/auth/route.config.ts +18 -18
- package/cli/templates/auth/route.handler.ts +18 -18
- package/cli/templates/auth/route.request-utils.ts +55 -55
- package/cli/templates/auth/route.ts +14 -14
- package/cli/templates/core/README.md +266 -266
- package/cli/templates/core/app-creation.ts +19 -19
- package/cli/templates/core/client/appflare.ts +112 -112
- package/cli/templates/core/client/handlers/index.ts +748 -748
- package/cli/templates/core/client/handlers.ts +1 -1
- package/cli/templates/core/client/index.ts +7 -7
- package/cli/templates/core/client/storage.ts +205 -180
- package/cli/templates/core/client/types.ts +186 -184
- package/cli/templates/core/client-modules/appflare.ts +1 -1
- package/cli/templates/core/client-modules/handlers.ts +1 -1
- package/cli/templates/core/client-modules/index.ts +1 -1
- package/cli/templates/core/client-modules/storage.ts +1 -1
- package/cli/templates/core/client-modules/types.ts +1 -1
- package/cli/templates/core/client.artifacts.ts +39 -39
- package/cli/templates/core/client.ts +4 -4
- package/cli/templates/core/drizzle.ts +15 -15
- package/cli/templates/core/export.ts +14 -14
- package/cli/templates/core/handlers.route.ts +24 -24
- package/cli/templates/core/handlers.ts +1 -1
- package/cli/templates/core/imports.ts +9 -9
- package/cli/templates/core/server.ts +38 -38
- package/cli/templates/core/types.ts +6 -6
- package/cli/templates/core/wrangler.ts +109 -109
- package/cli/templates/dashboard/builders/functions/index.ts +17 -17
- package/cli/templates/dashboard/builders/functions/render-page/header.ts +20 -20
- package/cli/templates/dashboard/builders/functions/render-page/index.ts +33 -33
- package/cli/templates/dashboard/builders/functions/render-page/request-panel.ts +171 -171
- package/cli/templates/dashboard/builders/functions/render-page/result-panel.ts +85 -85
- package/cli/templates/dashboard/builders/functions/render-page/scripts.ts +554 -554
- package/cli/templates/dashboard/builders/navigation.ts +122 -122
- package/cli/templates/dashboard/builders/storage/index.ts +13 -13
- package/cli/templates/dashboard/builders/storage/routes/create-directory-route.ts +29 -29
- package/cli/templates/dashboard/builders/storage/routes/delete-route.ts +18 -18
- package/cli/templates/dashboard/builders/storage/routes/download-route.ts +23 -23
- package/cli/templates/dashboard/builders/storage/routes/index.ts +22 -22
- package/cli/templates/dashboard/builders/storage/routes/list-route.ts +25 -25
- package/cli/templates/dashboard/builders/storage/routes/preview-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/routes/upload-route.ts +21 -21
- package/cli/templates/dashboard/builders/storage/runtime/helpers.ts +72 -72
- package/cli/templates/dashboard/builders/storage/runtime/storage-page.ts +130 -130
- package/cli/templates/dashboard/builders/table-routes/common/drawer-panel.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/common/pagination.ts +30 -30
- package/cli/templates/dashboard/builders/table-routes/common/search-bar.ts +23 -23
- package/cli/templates/dashboard/builders/table-routes/fragments.ts +217 -217
- package/cli/templates/dashboard/builders/table-routes/helpers.ts +45 -45
- package/cli/templates/dashboard/builders/table-routes/index.ts +8 -8
- package/cli/templates/dashboard/builders/table-routes/table/actions-cell.ts +71 -71
- package/cli/templates/dashboard/builders/table-routes/table/get-route.ts +291 -291
- package/cli/templates/dashboard/builders/table-routes/table/index.ts +80 -80
- package/cli/templates/dashboard/builders/table-routes/table/post-routes.ts +163 -163
- package/cli/templates/dashboard/builders/table-routes/table-route.ts +7 -7
- package/cli/templates/dashboard/builders/table-routes/users/get-route.ts +69 -69
- package/cli/templates/dashboard/builders/table-routes/users/html/modals.ts +57 -57
- package/cli/templates/dashboard/builders/table-routes/users/html/page.ts +27 -27
- package/cli/templates/dashboard/builders/table-routes/users/html/table.ts +128 -128
- package/cli/templates/dashboard/builders/table-routes/users/index.ts +32 -32
- package/cli/templates/dashboard/builders/table-routes/users/post-routes.ts +150 -150
- package/cli/templates/dashboard/builders/table-routes/users/redirect.ts +14 -14
- package/cli/templates/dashboard/builders/table-routes/users-route.ts +10 -10
- package/cli/templates/dashboard/components/dashboard-home.ts +23 -23
- package/cli/templates/dashboard/components/layout.ts +388 -388
- package/cli/templates/dashboard/components/login-page.ts +65 -65
- package/cli/templates/dashboard/index.ts +61 -61
- package/cli/templates/dashboard/types.ts +9 -9
- package/cli/templates/handlers/README.md +353 -353
- package/cli/templates/handlers/auth.ts +37 -37
- package/cli/templates/handlers/execution.ts +42 -42
- package/cli/templates/handlers/generators/context/context-creation.ts +101 -101
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -11
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -24
- package/cli/templates/handlers/generators/context/storage-api.ts +82 -112
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -59
- package/cli/templates/handlers/generators/context/types.ts +18 -18
- package/cli/templates/handlers/generators/context.ts +43 -43
- package/cli/templates/handlers/generators/execution.ts +15 -15
- package/cli/templates/handlers/generators/handlers.ts +13 -13
- package/cli/templates/handlers/generators/registration/modules/cron.ts +26 -26
- package/cli/templates/handlers/generators/registration/modules/realtime/auth.ts +75 -75
- package/cli/templates/handlers/generators/registration/modules/realtime/durable-object.ts +144 -144
- package/cli/templates/handlers/generators/registration/modules/realtime/index.ts +14 -14
- package/cli/templates/handlers/generators/registration/modules/realtime/publisher.ts +102 -102
- package/cli/templates/handlers/generators/registration/modules/realtime/routes.ts +164 -164
- package/cli/templates/handlers/generators/registration/modules/realtime/types.ts +30 -30
- package/cli/templates/handlers/generators/registration/modules/realtime/utils.ts +516 -516
- package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -56
- package/cli/templates/handlers/generators/registration/modules/storage.ts +192 -194
- package/cli/templates/handlers/generators/registration/sections.ts +210 -210
- package/cli/templates/handlers/generators/types/context.ts +67 -66
- package/cli/templates/handlers/generators/types/core.ts +106 -106
- package/cli/templates/handlers/generators/types/operations.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +259 -259
- package/cli/templates/handlers/generators/types/query-definitions/query-api-types.ts +135 -135
- package/cli/templates/handlers/generators/types/query-definitions/query-helper-functions.ts +1031 -1031
- package/cli/templates/handlers/generators/types/query-definitions/schema-and-table-types.ts +246 -246
- package/cli/templates/handlers/generators/types/query-definitions.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/handled-error.ts +13 -13
- package/cli/templates/handlers/generators/types/query-runtime/runtime-aggregate-and-footer.ts +174 -174
- package/cli/templates/handlers/generators/types/query-runtime/runtime-read.ts +121 -121
- package/cli/templates/handlers/generators/types/query-runtime/runtime-setup.ts +45 -45
- package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +676 -676
- package/cli/templates/handlers/generators/types/query-runtime.ts +15 -15
- package/cli/templates/handlers/index.ts +43 -43
- package/cli/templates/handlers/operations.ts +116 -116
- package/cli/templates/handlers/registration.ts +91 -91
- package/cli/templates/handlers/types.ts +15 -15
- package/cli/templates/handlers/utils.ts +48 -48
- package/cli/types.ts +110 -110
- package/cli/utils/handler-discovery.ts +466 -466
- package/cli/utils/json-utils.ts +24 -24
- package/cli/utils/path-utils.ts +19 -19
- package/cli/utils/schema-discovery.ts +399 -399
- package/dist/cli/index.js +95 -99
- package/dist/cli/index.mjs +95 -99
- package/index.ts +18 -18
- package/package.json +58 -58
- package/react/index.ts +5 -5
- package/react/use-infinite-query.ts +252 -252
- package/react/use-mutation.ts +89 -89
- package/react/use-query.ts +207 -207
- package/schema.ts +415 -415
- package/test-better-auth-hash.ts +2 -2
- package/tsconfig.json +6 -6
- package/tsup.config.ts +82 -82
- package/dist/cli/index.d.mts +0 -2
- package/dist/cli/index.d.ts +0 -2
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
export function buildStorageRuntimeHelpers(): string {
|
|
2
|
-
return `
|
|
3
|
-
const getStorageBucket = (c: any): R2Bucket | null => {
|
|
4
|
-
const r2Binding = (options as any).r2Binding;
|
|
5
|
-
if (!r2Binding || !c.env[r2Binding]) return null;
|
|
6
|
-
return c.env[r2Binding] as R2Bucket;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const renderStoragePage = (c: any, content: any) => {
|
|
10
|
-
return c.req.header('hx-request')
|
|
11
|
-
? c.html(content)
|
|
12
|
-
: c.html(Layout({ title: "Storage - Admin Dashboard", children: content }));
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const normalizePrefix = (value: string) => {
|
|
16
|
-
if (!value) return '';
|
|
17
|
-
const normalized = value
|
|
18
|
-
.split('/')
|
|
19
|
-
.map((part) => part.trim())
|
|
20
|
-
.filter(Boolean)
|
|
21
|
-
.join('/');
|
|
22
|
-
return normalized ? normalized + '/' : '';
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const decodeStoragePathSegment = (segment: string) => {
|
|
26
|
-
try {
|
|
27
|
-
return decodeURIComponent(segment);
|
|
28
|
-
} catch {
|
|
29
|
-
return segment;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const storagePathToPrefix = (path: string) => {
|
|
34
|
-
const raw = path.replace(/^\\/admin\\/storage\\/?/, '');
|
|
35
|
-
if (!raw) return '';
|
|
36
|
-
const decoded = raw
|
|
37
|
-
.split('/')
|
|
38
|
-
.filter(Boolean)
|
|
39
|
-
.map(decodeStoragePathSegment)
|
|
40
|
-
.join('/');
|
|
41
|
-
return normalizePrefix(decoded);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const prefixToStoragePath = (prefix: string) => {
|
|
45
|
-
const cleanPrefix = normalizePrefix(prefix);
|
|
46
|
-
if (!cleanPrefix) return '/admin/storage';
|
|
47
|
-
const encoded = cleanPrefix
|
|
48
|
-
.split('/')
|
|
49
|
-
.filter(Boolean)
|
|
50
|
-
.map((part) => encodeURIComponent(part))
|
|
51
|
-
.join('/');
|
|
52
|
-
return '/admin/storage/' + encoded;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const buildStorageNotConfiguredContent = () => html\`
|
|
56
|
-
<div class="flex flex-col items-center justify-center h-full gap-4 text-center">
|
|
57
|
-
<iconify-icon icon="solar:danger-triangle-bold-duotone" width="64" height="64" class="text-warning opacity-50"></iconify-icon>
|
|
58
|
-
<div>
|
|
59
|
-
<h3 class="text-lg font-semibold">Storage Not Configured</h3>
|
|
60
|
-
<p class="text-sm opacity-70 max-w-md mt-2">
|
|
61
|
-
R2 binding is not configured. Configure r2 in appflare.config.ts and regenerate artifacts to enable the Storage Manager.
|
|
62
|
-
</p>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
\`;
|
|
66
|
-
|
|
67
|
-
const getParentPrefix = (prefix: string) => {
|
|
68
|
-
const parts = prefix.split('/').filter(Boolean);
|
|
69
|
-
return parts.slice(0, -1).join('/') + (parts.length > 1 ? '/' : '');
|
|
70
|
-
};
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
1
|
+
export function buildStorageRuntimeHelpers(): string {
|
|
2
|
+
return `
|
|
3
|
+
const getStorageBucket = (c: any): R2Bucket | null => {
|
|
4
|
+
const r2Binding = (options as any).r2Binding;
|
|
5
|
+
if (!r2Binding || !c.env[r2Binding]) return null;
|
|
6
|
+
return c.env[r2Binding] as R2Bucket;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const renderStoragePage = (c: any, content: any) => {
|
|
10
|
+
return c.req.header('hx-request')
|
|
11
|
+
? c.html(content)
|
|
12
|
+
: c.html(Layout({ title: "Storage - Admin Dashboard", children: content }));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const normalizePrefix = (value: string) => {
|
|
16
|
+
if (!value) return '';
|
|
17
|
+
const normalized = value
|
|
18
|
+
.split('/')
|
|
19
|
+
.map((part) => part.trim())
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join('/');
|
|
22
|
+
return normalized ? normalized + '/' : '';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const decodeStoragePathSegment = (segment: string) => {
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(segment);
|
|
28
|
+
} catch {
|
|
29
|
+
return segment;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const storagePathToPrefix = (path: string) => {
|
|
34
|
+
const raw = path.replace(/^\\/admin\\/storage\\/?/, '');
|
|
35
|
+
if (!raw) return '';
|
|
36
|
+
const decoded = raw
|
|
37
|
+
.split('/')
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map(decodeStoragePathSegment)
|
|
40
|
+
.join('/');
|
|
41
|
+
return normalizePrefix(decoded);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const prefixToStoragePath = (prefix: string) => {
|
|
45
|
+
const cleanPrefix = normalizePrefix(prefix);
|
|
46
|
+
if (!cleanPrefix) return '/admin/storage';
|
|
47
|
+
const encoded = cleanPrefix
|
|
48
|
+
.split('/')
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.map((part) => encodeURIComponent(part))
|
|
51
|
+
.join('/');
|
|
52
|
+
return '/admin/storage/' + encoded;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const buildStorageNotConfiguredContent = () => html\`
|
|
56
|
+
<div class="flex flex-col items-center justify-center h-full gap-4 text-center">
|
|
57
|
+
<iconify-icon icon="solar:danger-triangle-bold-duotone" width="64" height="64" class="text-warning opacity-50"></iconify-icon>
|
|
58
|
+
<div>
|
|
59
|
+
<h3 class="text-lg font-semibold">Storage Not Configured</h3>
|
|
60
|
+
<p class="text-sm opacity-70 max-w-md mt-2">
|
|
61
|
+
R2 binding is not configured. Configure r2 in appflare.config.ts and regenerate artifacts to enable the Storage Manager.
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
\`;
|
|
66
|
+
|
|
67
|
+
const getParentPrefix = (prefix: string) => {
|
|
68
|
+
const parts = prefix.split('/').filter(Boolean);
|
|
69
|
+
return parts.slice(0, -1).join('/') + (parts.length > 1 ? '/' : '');
|
|
70
|
+
};
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
export function buildStoragePageRuntime(): string {
|
|
2
|
-
return `
|
|
3
|
-
const buildStorageListingContent = (listed: any, prefix: string) => {
|
|
4
|
-
const parts = prefix.split('/').filter(Boolean);
|
|
5
|
-
const breadcrumbs: any[] = [];
|
|
6
|
-
let currentPath = '';
|
|
7
|
-
const visibleObjects = listed.objects.filter((obj: any) => !obj.key.endsWith('/.keep'));
|
|
8
|
-
|
|
9
|
-
breadcrumbs.push(html\`<li><a href="/admin/storage" hx-get="/admin/storage" hx-target="#main-content" hx-push-url="true">Root</a></li>\`);
|
|
10
|
-
for (const part of parts) {
|
|
11
|
-
currentPath += part + '/';
|
|
12
|
-
const partPath = prefixToStoragePath(currentPath);
|
|
13
|
-
breadcrumbs.push(html\`<li><a href="\${partPath}" hx-get="\${partPath}" hx-target="#main-content" hx-push-url="true">\${part}</a></li>\`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const formatBytes = (bytes: number) => {
|
|
17
|
-
if (bytes === 0) return '0 B';
|
|
18
|
-
const k = 1024;
|
|
19
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
20
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
21
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
return html\`
|
|
25
|
-
<div id="main-content" class="h-full flex flex-col">
|
|
26
|
-
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-5 gap-3">
|
|
27
|
-
<div class="text-sm breadcrumbs p-0">
|
|
28
|
-
<ul>\${breadcrumbs}</ul>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="flex items-center gap-2">
|
|
31
|
-
<form hx-post="/admin/storage/upload" hx-encoding="multipart/form-data" hx-target="#main-content" hx-swap="outerHTML" class="flex items-center gap-2">
|
|
32
|
-
<input type="hidden" name="prefix" value="\${prefix}" />
|
|
33
|
-
<input type="file" name="file" id="file-upload" class="hidden" onchange="this.form.requestSubmit()" />
|
|
34
|
-
<label for="file-upload" class="btn btn-primary btn-sm gap-1 cursor-pointer">
|
|
35
|
-
<iconify-icon icon="solar:upload-minimalistic-bold-duotone" width="16" height="16"></iconify-icon>
|
|
36
|
-
Upload File
|
|
37
|
-
</label>
|
|
38
|
-
</form>
|
|
39
|
-
<form hx-post="/admin/storage/directory" hx-target="#main-content" hx-swap="outerHTML" class="flex items-center gap-2">
|
|
40
|
-
<input type="hidden" name="prefix" value="\${prefix}" />
|
|
41
|
-
<input type="text" name="directory" placeholder="New folder" class="input input-sm input-bordered w-32 md:w-44" required />
|
|
42
|
-
<button type="submit" class="btn btn-outline btn-sm gap-1">
|
|
43
|
-
<iconify-icon icon="solar:add-folder-bold-duotone" width="16" height="16"></iconify-icon>
|
|
44
|
-
Create
|
|
45
|
-
</button>
|
|
46
|
-
</form>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div class="bg-base-100 flex-1 rounded-xl border border-base-200 overflow-hidden flex flex-col pt-1">
|
|
51
|
-
<div class="overflow-x-auto">
|
|
52
|
-
<table class="table table-sm md:table-md w-full">
|
|
53
|
-
<thead>
|
|
54
|
-
<tr class="border-b border-base-200">
|
|
55
|
-
<th>Name</th>
|
|
56
|
-
<th class="w-32">Size</th>
|
|
57
|
-
<th class="w-48 text-right">Modified</th>
|
|
58
|
-
<th class="w-32 text-right">Actions</th>
|
|
59
|
-
</tr>
|
|
60
|
-
</thead>
|
|
61
|
-
<tbody>
|
|
62
|
-
\${prefix ? html\`
|
|
63
|
-
<tr class="hover:bg-base-200/30 transition-colors">
|
|
64
|
-
<td colspan="4">
|
|
65
|
-
<a href="\${prefixToStoragePath(getParentPrefix(prefix))}" hx-get="\${prefixToStoragePath(getParentPrefix(prefix))}" hx-target="#main-content" hx-push-url="true" class="flex items-center gap-2 text-sm">
|
|
66
|
-
<iconify-icon icon="solar:folder-bold-duotone" width="20" height="20" class="text-primary opacity-60"></iconify-icon>
|
|
67
|
-
..
|
|
68
|
-
</a>
|
|
69
|
-
</td>
|
|
70
|
-
</tr>
|
|
71
|
-
\` : ''}
|
|
72
|
-
\${listed.delimitedPrefixes.map((dir: string) => html\`
|
|
73
|
-
<tr class="hover:bg-base-200/30 transition-colors">
|
|
74
|
-
<td>
|
|
75
|
-
<a href="\${prefixToStoragePath(dir)}" hx-get="\${prefixToStoragePath(dir)}" hx-target="#main-content" hx-push-url="true" class="flex items-center gap-2 text-sm font-medium" data-storage-dir-link>
|
|
76
|
-
<iconify-icon icon="solar:folder-bold-duotone" width="20" height="20" class="text-primary"></iconify-icon>
|
|
77
|
-
\${dir.slice(prefix.length, -1)}
|
|
78
|
-
</a>
|
|
79
|
-
</td>
|
|
80
|
-
<td class="text-xs opacity-50">-</td>
|
|
81
|
-
<td class="text-xs opacity-50 text-right">-</td>
|
|
82
|
-
<td></td>
|
|
83
|
-
</tr>
|
|
84
|
-
\`)}
|
|
85
|
-
\${visibleObjects.map((obj: any) => html\`
|
|
86
|
-
<tr class="hover:bg-base-200/30 transition-colors group">
|
|
87
|
-
<td>
|
|
88
|
-
<div class="flex items-center gap-2">
|
|
89
|
-
\${obj.key.match(/\.(jpg|jpeg|png|gif|webp|svg|bmp)$/i)
|
|
90
|
-
? html\`<div class="w-8 h-8 rounded shrink-0 bg-base-200 overflow-hidden object-cover bg-center" style="background-image: url('/admin/storage/preview?key=\${encodeURIComponent(obj.key)}')"></div>\`
|
|
91
|
-
: html\`<iconify-icon icon="solar:document-bold-duotone" width="20" height="20" class="text-base-content opacity-40"></iconify-icon>\`}
|
|
92
|
-
<span class="text-sm truncate max-w-[200px] md:max-w-md" title="\${obj.key.slice(prefix.length)}">\${obj.key.slice(prefix.length)}</span>
|
|
93
|
-
</div>
|
|
94
|
-
</td>
|
|
95
|
-
<td class="text-xs opacity-60">\${formatBytes(obj.size)}</td>
|
|
96
|
-
<td class="text-xs opacity-60 text-right">\${new Date(obj.uploaded).toLocaleString()}</td>
|
|
97
|
-
<td class="text-right">
|
|
98
|
-
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
99
|
-
<a href="/admin/storage/preview?key=\${encodeURIComponent(obj.key)}" target="_blank" class="btn btn-ghost btn-xs btn-square" title="Preview">
|
|
100
|
-
<iconify-icon icon="solar:eye-linear" width="16" height="16"></iconify-icon>
|
|
101
|
-
</a>
|
|
102
|
-
<a href="/admin/storage/download?key=\${encodeURIComponent(obj.key)}" target="_blank" class="btn btn-ghost btn-xs btn-square" title="Download">
|
|
103
|
-
<iconify-icon icon="solar:download-linear" width="16" height="16"></iconify-icon>
|
|
104
|
-
</a>
|
|
105
|
-
<button hx-delete="/admin/storage/delete?key=\${encodeURIComponent(obj.key)}&prefix=\${encodeURIComponent(prefix)}" hx-target="#main-content" hx-swap="outerHTML" hx-confirm="Are you sure you want to delete this file?" class="btn btn-ghost btn-xs btn-square text-error" title="Delete">
|
|
106
|
-
<iconify-icon icon="solar:trash-bin-trash-linear" width="16" height="16"></iconify-icon>
|
|
107
|
-
</button>
|
|
108
|
-
</div>
|
|
109
|
-
</td>
|
|
110
|
-
</tr>
|
|
111
|
-
\`)}
|
|
112
|
-
\${listed.delimitedPrefixes.length === 0 && visibleObjects.length === 0 ? html\`
|
|
113
|
-
<tr>
|
|
114
|
-
<td colspan="4" class="text-center py-12">
|
|
115
|
-
<div class="flex flex-col items-center gap-2 opacity-40">
|
|
116
|
-
<iconify-icon icon="solar:folder-open-bold-duotone" width="48" height="48"></iconify-icon>
|
|
117
|
-
<p class="text-sm">This folder is empty</p>
|
|
118
|
-
</div>
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
\` : ''}
|
|
122
|
-
</tbody>
|
|
123
|
-
</table>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
\`;
|
|
128
|
-
};
|
|
129
|
-
`;
|
|
130
|
-
}
|
|
1
|
+
export function buildStoragePageRuntime(): string {
|
|
2
|
+
return `
|
|
3
|
+
const buildStorageListingContent = (listed: any, prefix: string) => {
|
|
4
|
+
const parts = prefix.split('/').filter(Boolean);
|
|
5
|
+
const breadcrumbs: any[] = [];
|
|
6
|
+
let currentPath = '';
|
|
7
|
+
const visibleObjects = listed.objects.filter((obj: any) => !obj.key.endsWith('/.keep'));
|
|
8
|
+
|
|
9
|
+
breadcrumbs.push(html\`<li><a href="/admin/storage" hx-get="/admin/storage" hx-target="#main-content" hx-push-url="true">Root</a></li>\`);
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
currentPath += part + '/';
|
|
12
|
+
const partPath = prefixToStoragePath(currentPath);
|
|
13
|
+
breadcrumbs.push(html\`<li><a href="\${partPath}" hx-get="\${partPath}" hx-target="#main-content" hx-push-url="true">\${part}</a></li>\`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const formatBytes = (bytes: number) => {
|
|
17
|
+
if (bytes === 0) return '0 B';
|
|
18
|
+
const k = 1024;
|
|
19
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
20
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
21
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return html\`
|
|
25
|
+
<div id="main-content" class="h-full flex flex-col">
|
|
26
|
+
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-5 gap-3">
|
|
27
|
+
<div class="text-sm breadcrumbs p-0">
|
|
28
|
+
<ul>\${breadcrumbs}</ul>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex items-center gap-2">
|
|
31
|
+
<form hx-post="/admin/storage/upload" hx-encoding="multipart/form-data" hx-target="#main-content" hx-swap="outerHTML" class="flex items-center gap-2">
|
|
32
|
+
<input type="hidden" name="prefix" value="\${prefix}" />
|
|
33
|
+
<input type="file" name="file" id="file-upload" class="hidden" onchange="this.form.requestSubmit()" />
|
|
34
|
+
<label for="file-upload" class="btn btn-primary btn-sm gap-1 cursor-pointer">
|
|
35
|
+
<iconify-icon icon="solar:upload-minimalistic-bold-duotone" width="16" height="16"></iconify-icon>
|
|
36
|
+
Upload File
|
|
37
|
+
</label>
|
|
38
|
+
</form>
|
|
39
|
+
<form hx-post="/admin/storage/directory" hx-target="#main-content" hx-swap="outerHTML" class="flex items-center gap-2">
|
|
40
|
+
<input type="hidden" name="prefix" value="\${prefix}" />
|
|
41
|
+
<input type="text" name="directory" placeholder="New folder" class="input input-sm input-bordered w-32 md:w-44" required />
|
|
42
|
+
<button type="submit" class="btn btn-outline btn-sm gap-1">
|
|
43
|
+
<iconify-icon icon="solar:add-folder-bold-duotone" width="16" height="16"></iconify-icon>
|
|
44
|
+
Create
|
|
45
|
+
</button>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="bg-base-100 flex-1 rounded-xl border border-base-200 overflow-hidden flex flex-col pt-1">
|
|
51
|
+
<div class="overflow-x-auto">
|
|
52
|
+
<table class="table table-sm md:table-md w-full">
|
|
53
|
+
<thead>
|
|
54
|
+
<tr class="border-b border-base-200">
|
|
55
|
+
<th>Name</th>
|
|
56
|
+
<th class="w-32">Size</th>
|
|
57
|
+
<th class="w-48 text-right">Modified</th>
|
|
58
|
+
<th class="w-32 text-right">Actions</th>
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
\${prefix ? html\`
|
|
63
|
+
<tr class="hover:bg-base-200/30 transition-colors">
|
|
64
|
+
<td colspan="4">
|
|
65
|
+
<a href="\${prefixToStoragePath(getParentPrefix(prefix))}" hx-get="\${prefixToStoragePath(getParentPrefix(prefix))}" hx-target="#main-content" hx-push-url="true" class="flex items-center gap-2 text-sm">
|
|
66
|
+
<iconify-icon icon="solar:folder-bold-duotone" width="20" height="20" class="text-primary opacity-60"></iconify-icon>
|
|
67
|
+
..
|
|
68
|
+
</a>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
\` : ''}
|
|
72
|
+
\${listed.delimitedPrefixes.map((dir: string) => html\`
|
|
73
|
+
<tr class="hover:bg-base-200/30 transition-colors">
|
|
74
|
+
<td>
|
|
75
|
+
<a href="\${prefixToStoragePath(dir)}" hx-get="\${prefixToStoragePath(dir)}" hx-target="#main-content" hx-push-url="true" class="flex items-center gap-2 text-sm font-medium" data-storage-dir-link>
|
|
76
|
+
<iconify-icon icon="solar:folder-bold-duotone" width="20" height="20" class="text-primary"></iconify-icon>
|
|
77
|
+
\${dir.slice(prefix.length, -1)}
|
|
78
|
+
</a>
|
|
79
|
+
</td>
|
|
80
|
+
<td class="text-xs opacity-50">-</td>
|
|
81
|
+
<td class="text-xs opacity-50 text-right">-</td>
|
|
82
|
+
<td></td>
|
|
83
|
+
</tr>
|
|
84
|
+
\`)}
|
|
85
|
+
\${visibleObjects.map((obj: any) => html\`
|
|
86
|
+
<tr class="hover:bg-base-200/30 transition-colors group">
|
|
87
|
+
<td>
|
|
88
|
+
<div class="flex items-center gap-2">
|
|
89
|
+
\${obj.key.match(/\.(jpg|jpeg|png|gif|webp|svg|bmp)$/i)
|
|
90
|
+
? html\`<div class="w-8 h-8 rounded shrink-0 bg-base-200 overflow-hidden object-cover bg-center" style="background-image: url('/admin/storage/preview?key=\${encodeURIComponent(obj.key)}')"></div>\`
|
|
91
|
+
: html\`<iconify-icon icon="solar:document-bold-duotone" width="20" height="20" class="text-base-content opacity-40"></iconify-icon>\`}
|
|
92
|
+
<span class="text-sm truncate max-w-[200px] md:max-w-md" title="\${obj.key.slice(prefix.length)}">\${obj.key.slice(prefix.length)}</span>
|
|
93
|
+
</div>
|
|
94
|
+
</td>
|
|
95
|
+
<td class="text-xs opacity-60">\${formatBytes(obj.size)}</td>
|
|
96
|
+
<td class="text-xs opacity-60 text-right">\${new Date(obj.uploaded).toLocaleString()}</td>
|
|
97
|
+
<td class="text-right">
|
|
98
|
+
<div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
99
|
+
<a href="/admin/storage/preview?key=\${encodeURIComponent(obj.key)}" target="_blank" class="btn btn-ghost btn-xs btn-square" title="Preview">
|
|
100
|
+
<iconify-icon icon="solar:eye-linear" width="16" height="16"></iconify-icon>
|
|
101
|
+
</a>
|
|
102
|
+
<a href="/admin/storage/download?key=\${encodeURIComponent(obj.key)}" target="_blank" class="btn btn-ghost btn-xs btn-square" title="Download">
|
|
103
|
+
<iconify-icon icon="solar:download-linear" width="16" height="16"></iconify-icon>
|
|
104
|
+
</a>
|
|
105
|
+
<button hx-delete="/admin/storage/delete?key=\${encodeURIComponent(obj.key)}&prefix=\${encodeURIComponent(prefix)}" hx-target="#main-content" hx-swap="outerHTML" hx-confirm="Are you sure you want to delete this file?" class="btn btn-ghost btn-xs btn-square text-error" title="Delete">
|
|
106
|
+
<iconify-icon icon="solar:trash-bin-trash-linear" width="16" height="16"></iconify-icon>
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
\`)}
|
|
112
|
+
\${listed.delimitedPrefixes.length === 0 && visibleObjects.length === 0 ? html\`
|
|
113
|
+
<tr>
|
|
114
|
+
<td colspan="4" class="text-center py-12">
|
|
115
|
+
<div class="flex flex-col items-center gap-2 opacity-40">
|
|
116
|
+
<iconify-icon icon="solar:folder-open-bold-duotone" width="48" height="48"></iconify-icon>
|
|
117
|
+
<p class="text-sm">This folder is empty</p>
|
|
118
|
+
</div>
|
|
119
|
+
</td>
|
|
120
|
+
</tr>
|
|
121
|
+
\` : ''}
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
\`;
|
|
128
|
+
};
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps content inside a DaisyUI drawer-side panel.
|
|
3
|
-
* Clean PocketBase-style side panel.
|
|
4
|
-
*
|
|
5
|
-
* @param drawerId - the checkbox input id used for toggling the drawer
|
|
6
|
-
* @param title - heading shown at the top of the panel
|
|
7
|
-
* @param body - raw HTML string to embed inside the panel body
|
|
8
|
-
*/
|
|
9
|
-
export function buildDrawerPanel(
|
|
10
|
-
drawerId: string,
|
|
11
|
-
title: string,
|
|
12
|
-
body: string,
|
|
13
|
-
): string {
|
|
14
|
-
return `
|
|
15
|
-
\t\t<div class="drawer-side z-50">
|
|
16
|
-
\t\t\t<label for="${drawerId}" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
17
|
-
\t\t\t<div class="w-full max-w-md min-h-full bg-base-100 p-6 border-l border-base-200 overflow-y-auto shadow-lg">
|
|
18
|
-
\t\t\t\t<div class="flex justify-between items-center mb-5">
|
|
19
|
-
\t\t\t\t\t<h3 class="text-base font-semibold">${title}</h3>
|
|
20
|
-
\t\t\t\t\t<label for="${drawerId}" class="btn btn-sm btn-ghost btn-square">
|
|
21
|
-
\t\t\t\t\t\t<iconify-icon icon="mdi:close" width="18" height="18"></iconify-icon>
|
|
22
|
-
\t\t\t\t\t</label>
|
|
23
|
-
\t\t\t\t</div>
|
|
24
|
-
\t\t\t\t${body}
|
|
25
|
-
\t\t\t</div>
|
|
26
|
-
\t\t</div>`;
|
|
27
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Wraps content inside a DaisyUI drawer-side panel.
|
|
3
|
+
* Clean PocketBase-style side panel.
|
|
4
|
+
*
|
|
5
|
+
* @param drawerId - the checkbox input id used for toggling the drawer
|
|
6
|
+
* @param title - heading shown at the top of the panel
|
|
7
|
+
* @param body - raw HTML string to embed inside the panel body
|
|
8
|
+
*/
|
|
9
|
+
export function buildDrawerPanel(
|
|
10
|
+
drawerId: string,
|
|
11
|
+
title: string,
|
|
12
|
+
body: string,
|
|
13
|
+
): string {
|
|
14
|
+
return `
|
|
15
|
+
\t\t<div class="drawer-side z-50">
|
|
16
|
+
\t\t\t<label for="${drawerId}" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
17
|
+
\t\t\t<div class="w-full max-w-md min-h-full bg-base-100 p-6 border-l border-base-200 overflow-y-auto shadow-lg">
|
|
18
|
+
\t\t\t\t<div class="flex justify-between items-center mb-5">
|
|
19
|
+
\t\t\t\t\t<h3 class="text-base font-semibold">${title}</h3>
|
|
20
|
+
\t\t\t\t\t<label for="${drawerId}" class="btn btn-sm btn-ghost btn-square">
|
|
21
|
+
\t\t\t\t\t\t<iconify-icon icon="mdi:close" width="18" height="18"></iconify-icon>
|
|
22
|
+
\t\t\t\t\t</label>
|
|
23
|
+
\t\t\t\t</div>
|
|
24
|
+
\t\t\t\t${body}
|
|
25
|
+
\t\t\t</div>
|
|
26
|
+
\t\t</div>`;
|
|
27
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the pagination HTML string (runtime template literal) for a given route prefix.
|
|
3
|
-
* PocketBase-style minimal footer with total count.
|
|
4
|
-
* @param routePrefix - e.g. "/admin/users" or "/admin/table/myTable"
|
|
5
|
-
*/
|
|
6
|
-
export function buildPaginationHtml(routePrefix: string): string {
|
|
7
|
-
return `
|
|
8
|
-
\t\t<div class="flex flex-col sm:flex-row justify-between items-center mt-4 gap-3 py-3 px-1">
|
|
9
|
-
\t\t\t<div class="text-xs text-base-content/40">
|
|
10
|
-
\t\t\t\tTotal found: <span class="font-medium text-base-content/60">\${total}</span>
|
|
11
|
-
\t\t\t</div>
|
|
12
|
-
\t\t\t\${totalPages > 1 ? html\`
|
|
13
|
-
\t\t\t<div class="join border border-base-200 rounded-lg">
|
|
14
|
-
\t\t\t\t\${page > 1 ? html\`
|
|
15
|
-
\t\t\t\t\t<button hx-get="${routePrefix}?page=\${page - 1}&search=\${search}&sort=\${sort}&order=\${order}" hx-target="#main-content" hx-push-url="true" class="join-item btn btn-sm btn-ghost">
|
|
16
|
-
\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-left" width="16" height="16"></iconify-icon>
|
|
17
|
-
\t\t\t\t\t</button>
|
|
18
|
-
\t\t\t\t\` : html\`<button class="join-item btn btn-sm btn-ghost btn-disabled"><iconify-icon icon="mdi:chevron-left" width="16" height="16"></iconify-icon></button>\`}
|
|
19
|
-
|
|
20
|
-
\t\t\t\t<button class="join-item btn btn-sm btn-ghost no-animation pointer-events-none text-xs">\${page} / \${totalPages}</button>
|
|
21
|
-
|
|
22
|
-
\t\t\t\t\${page < totalPages ? html\`
|
|
23
|
-
\t\t\t\t\t<button hx-get="${routePrefix}?page=\${page + 1}&search=\${search}&sort=\${sort}&order=\${order}" hx-target="#main-content" hx-push-url="true" class="join-item btn btn-sm btn-ghost">
|
|
24
|
-
\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-right" width="16" height="16"></iconify-icon>
|
|
25
|
-
\t\t\t\t\t</button>
|
|
26
|
-
\t\t\t\t\` : html\`<button class="join-item btn btn-sm btn-ghost btn-disabled"><iconify-icon icon="mdi:chevron-right" width="16" height="16"></iconify-icon></button>\`}
|
|
27
|
-
\t\t\t</div>
|
|
28
|
-
\t\t\t\` : ''}
|
|
29
|
-
\t\t</div>`;
|
|
30
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Builds the pagination HTML string (runtime template literal) for a given route prefix.
|
|
3
|
+
* PocketBase-style minimal footer with total count.
|
|
4
|
+
* @param routePrefix - e.g. "/admin/users" or "/admin/table/myTable"
|
|
5
|
+
*/
|
|
6
|
+
export function buildPaginationHtml(routePrefix: string): string {
|
|
7
|
+
return `
|
|
8
|
+
\t\t<div class="flex flex-col sm:flex-row justify-between items-center mt-4 gap-3 py-3 px-1">
|
|
9
|
+
\t\t\t<div class="text-xs text-base-content/40">
|
|
10
|
+
\t\t\t\tTotal found: <span class="font-medium text-base-content/60">\${total}</span>
|
|
11
|
+
\t\t\t</div>
|
|
12
|
+
\t\t\t\${totalPages > 1 ? html\`
|
|
13
|
+
\t\t\t<div class="join border border-base-200 rounded-lg">
|
|
14
|
+
\t\t\t\t\${page > 1 ? html\`
|
|
15
|
+
\t\t\t\t\t<button hx-get="${routePrefix}?page=\${page - 1}&search=\${search}&sort=\${sort}&order=\${order}" hx-target="#main-content" hx-push-url="true" class="join-item btn btn-sm btn-ghost">
|
|
16
|
+
\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-left" width="16" height="16"></iconify-icon>
|
|
17
|
+
\t\t\t\t\t</button>
|
|
18
|
+
\t\t\t\t\` : html\`<button class="join-item btn btn-sm btn-ghost btn-disabled"><iconify-icon icon="mdi:chevron-left" width="16" height="16"></iconify-icon></button>\`}
|
|
19
|
+
|
|
20
|
+
\t\t\t\t<button class="join-item btn btn-sm btn-ghost no-animation pointer-events-none text-xs">\${page} / \${totalPages}</button>
|
|
21
|
+
|
|
22
|
+
\t\t\t\t\${page < totalPages ? html\`
|
|
23
|
+
\t\t\t\t\t<button hx-get="${routePrefix}?page=\${page + 1}&search=\${search}&sort=\${sort}&order=\${order}" hx-target="#main-content" hx-push-url="true" class="join-item btn btn-sm btn-ghost">
|
|
24
|
+
\t\t\t\t\t\t<iconify-icon icon="mdi:chevron-right" width="16" height="16"></iconify-icon>
|
|
25
|
+
\t\t\t\t\t</button>
|
|
26
|
+
\t\t\t\t\` : html\`<button class="join-item btn btn-sm btn-ghost btn-disabled"><iconify-icon icon="mdi:chevron-right" width="16" height="16"></iconify-icon></button>\`}
|
|
27
|
+
\t\t\t</div>
|
|
28
|
+
\t\t\t\` : ''}
|
|
29
|
+
\t\t</div>`;
|
|
30
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the search input HTML string (runtime template literal) for a given route prefix.
|
|
3
|
-
* PocketBase-style full-width filter bar.
|
|
4
|
-
* @param routePrefix - e.g. "/admin/users" or "/admin/table/myTable"
|
|
5
|
-
* @param placeholder - placeholder text for the input
|
|
6
|
-
*/
|
|
7
|
-
export function buildSearchBarHtml(
|
|
8
|
-
routePrefix: string,
|
|
9
|
-
placeholder = "Search term or filter...",
|
|
10
|
-
): string {
|
|
11
|
-
return `
|
|
12
|
-
\t\t\t<div class="form-control w-full md:w-auto relative">
|
|
13
|
-
\t\t\t\t<iconify-icon icon="mdi:magnify" width="18" height="18" class="absolute left-3 top-1/2 -translate-y-1/2 opacity-40"></iconify-icon>
|
|
14
|
-
\t\t\t\t<input type="text"
|
|
15
|
-
\t\t\t\t\tname="search"
|
|
16
|
-
\t\t\t\t\tplaceholder="${placeholder}"
|
|
17
|
-
\t\t\t\t\tvalue="\${search}"
|
|
18
|
-
\t\t\t\t\thx-get="${routePrefix}?sort=\${sort}&order=\${order}"
|
|
19
|
-
\t\t\t\t\thx-trigger="keyup changed delay:500ms, search"
|
|
20
|
-
\t\t\t\t\thx-target="#main-content"
|
|
21
|
-
\t\t\t\t\tclass="input input-sm md:input-md input-bordered pl-9 w-full md:w-72 bg-base-200/50 border-base-200 focus:bg-base-100 focus:border-primary transition-all text-sm" />
|
|
22
|
-
\t\t\t</div>`;
|
|
23
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Builds the search input HTML string (runtime template literal) for a given route prefix.
|
|
3
|
+
* PocketBase-style full-width filter bar.
|
|
4
|
+
* @param routePrefix - e.g. "/admin/users" or "/admin/table/myTable"
|
|
5
|
+
* @param placeholder - placeholder text for the input
|
|
6
|
+
*/
|
|
7
|
+
export function buildSearchBarHtml(
|
|
8
|
+
routePrefix: string,
|
|
9
|
+
placeholder = "Search term or filter...",
|
|
10
|
+
): string {
|
|
11
|
+
return `
|
|
12
|
+
\t\t\t<div class="form-control w-full md:w-auto relative">
|
|
13
|
+
\t\t\t\t<iconify-icon icon="mdi:magnify" width="18" height="18" class="absolute left-3 top-1/2 -translate-y-1/2 opacity-40"></iconify-icon>
|
|
14
|
+
\t\t\t\t<input type="text"
|
|
15
|
+
\t\t\t\t\tname="search"
|
|
16
|
+
\t\t\t\t\tplaceholder="${placeholder}"
|
|
17
|
+
\t\t\t\t\tvalue="\${search}"
|
|
18
|
+
\t\t\t\t\thx-get="${routePrefix}?sort=\${sort}&order=\${order}"
|
|
19
|
+
\t\t\t\t\thx-trigger="keyup changed delay:500ms, search"
|
|
20
|
+
\t\t\t\t\thx-target="#main-content"
|
|
21
|
+
\t\t\t\t\tclass="input input-sm md:input-md input-bordered pl-9 w-full md:w-72 bg-base-200/50 border-base-200 focus:bg-base-100 focus:border-primary transition-all text-sm" />
|
|
22
|
+
\t\t\t</div>`;
|
|
23
|
+
}
|