kempo 4.2.9 → 4.2.11
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/app-public/.config.json +2 -5
- package/bin/cli.js +4 -10
- package/dist/admin/accounts/groups/[name]/index.html +518 -0
- package/dist/admin/accounts/groups/index.html +378 -0
- package/dist/admin/accounts/permissions/index.html +397 -0
- package/dist/admin/accounts/users/[id]/index.html +541 -0
- package/dist/admin/accounts/users/index.html +396 -0
- package/dist/admin/content/fragments/edit/index.html +132 -0
- package/dist/admin/content/fragments/index.html +375 -0
- package/dist/admin/content/globals/edit/index.html +132 -0
- package/dist/admin/content/globals/index.html +358 -0
- package/dist/admin/content/pages/edit/index.html +132 -0
- package/dist/admin/content/pages/index.html +509 -0
- package/dist/admin/content/templates/edit/index.html +132 -0
- package/dist/admin/content/templates/index.html +429 -0
- package/dist/admin/extensions/index.html +222 -0
- package/dist/admin/index.html +130 -0
- package/dist/admin/index.page.html +1 -0
- package/dist/admin/settings/[owner]/[name]/index.html +449 -0
- package/dist/admin/settings/index.html +410 -0
- package/middleware/{extension-scope-router.js → kempo.js} +40 -25
- package/package.json +3 -3
- package/middleware/kempo-auth.js +0 -26
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
owner: system
|
|
3
|
+
locked: true
|
|
4
|
+
name:
|
|
5
|
+
author:
|
|
6
|
+
created:
|
|
7
|
+
updated:
|
|
8
|
+
-->
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<link rel="icon" type="image/png" sizes="48x48" href="/media/icon48.png">
|
|
15
|
+
<link rel="manifest" href="/manifest.json">
|
|
16
|
+
<title>Fragments - Admin</title>
|
|
17
|
+
<link rel="stylesheet" href="/kempo-css/kempo.min.css">
|
|
18
|
+
<script src="/admin/init.js"></script>
|
|
19
|
+
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<!--
|
|
23
|
+
locked: true
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
<k-aside
|
|
27
|
+
main="push"
|
|
28
|
+
state="expanded"
|
|
29
|
+
style="--aside_padding: 0;"
|
|
30
|
+
persistent-id="admin-nav"
|
|
31
|
+
>
|
|
32
|
+
<k-aside-toggle>
|
|
33
|
+
<h3 class="m0">Kempo Admin</h3>
|
|
34
|
+
</k-aside-toggle>
|
|
35
|
+
<k-aside-item
|
|
36
|
+
icon="preview"
|
|
37
|
+
href="/"
|
|
38
|
+
no-expand
|
|
39
|
+
>Site</k-aside-item>
|
|
40
|
+
<k-aside-item
|
|
41
|
+
icon="cards"
|
|
42
|
+
href="/admin"
|
|
43
|
+
no-expand
|
|
44
|
+
>Dashboard</k-aside-item>
|
|
45
|
+
<k-aside-menu
|
|
46
|
+
icon="manage-accounts"
|
|
47
|
+
label="Accounts"
|
|
48
|
+
>
|
|
49
|
+
<k-aside-item
|
|
50
|
+
icon="person"
|
|
51
|
+
href="/admin/accounts/users"
|
|
52
|
+
no-expand
|
|
53
|
+
>Users</k-aside-item>
|
|
54
|
+
<k-aside-item
|
|
55
|
+
icon="group"
|
|
56
|
+
href="/admin/accounts/groups"
|
|
57
|
+
no-expand
|
|
58
|
+
>Groups</k-aside-item>
|
|
59
|
+
<k-aside-item
|
|
60
|
+
icon="lock"
|
|
61
|
+
href="/admin/accounts/permissions"
|
|
62
|
+
no-expand
|
|
63
|
+
>Permissions</k-aside-item>
|
|
64
|
+
</k-aside-menu>
|
|
65
|
+
<k-aside-menu
|
|
66
|
+
icon="cards_stack"
|
|
67
|
+
label="Content"
|
|
68
|
+
>
|
|
69
|
+
<k-aside-item
|
|
70
|
+
icon="browse"
|
|
71
|
+
href="/admin/content/templates"
|
|
72
|
+
>Templates</k-aside-item>
|
|
73
|
+
<k-aside-item
|
|
74
|
+
icon="docs"
|
|
75
|
+
href="/admin/content/pages"
|
|
76
|
+
>Pages</k-aside-item>
|
|
77
|
+
<k-aside-item
|
|
78
|
+
icon="content"
|
|
79
|
+
href="/admin/content/fragments"
|
|
80
|
+
>Fragments</k-aside-item>
|
|
81
|
+
<k-aside-item
|
|
82
|
+
icon="globe"
|
|
83
|
+
href="/admin/content/globals"
|
|
84
|
+
>Global Content</k-aside-item>
|
|
85
|
+
</k-aside-menu>
|
|
86
|
+
<k-aside-item
|
|
87
|
+
icon="settings"
|
|
88
|
+
href="/admin/settings"
|
|
89
|
+
no-expand
|
|
90
|
+
>Settings</k-aside-item>
|
|
91
|
+
<k-aside-item
|
|
92
|
+
icon="extension"
|
|
93
|
+
href="/admin/extensions"
|
|
94
|
+
no-expand
|
|
95
|
+
>Extensions</k-aside-item>
|
|
96
|
+
<hr>
|
|
97
|
+
<k-aside-spacer></k-aside-spacer>
|
|
98
|
+
<k-theme-switcher
|
|
99
|
+
labels
|
|
100
|
+
style="--padding:var(--spacer_h);margin:var(--spacer_h) auto"
|
|
101
|
+
></k-theme-switcher>
|
|
102
|
+
</k-aside>
|
|
103
|
+
<script type="module">
|
|
104
|
+
import '/kempo-ui/components/ThemeSwitcher.js';
|
|
105
|
+
const items = document.querySelectorAll('k-aside-item');
|
|
106
|
+
const currentPath = window.location.pathname;
|
|
107
|
+
|
|
108
|
+
items.forEach(item => {
|
|
109
|
+
const href = item.getAttribute('href');
|
|
110
|
+
if (!href) return;
|
|
111
|
+
const isExact = href === '/admin' || href === '/';
|
|
112
|
+
const match = isExact
|
|
113
|
+
? (currentPath === href || currentPath === href + '/')
|
|
114
|
+
: (currentPath === href || currentPath.startsWith(href + '/'));
|
|
115
|
+
if (match) {
|
|
116
|
+
item.setAttribute('active', '');
|
|
117
|
+
const menu = item.closest('k-aside-menu');
|
|
118
|
+
if (menu) menu.setAttribute('open', '');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<k-main>
|
|
124
|
+
|
|
125
|
+
<script type="module" src="/kempo-ui/components/Table.js"></script>
|
|
126
|
+
<script type="module" src="/kempo-ui/components/tableControls/FirstPage.js"></script>
|
|
127
|
+
<script type="module" src="/kempo-ui/components/tableControls/PrevPage.js"></script>
|
|
128
|
+
<script type="module" src="/kempo-ui/components/tableControls/NextPage.js"></script>
|
|
129
|
+
<script type="module" src="/kempo-ui/components/tableControls/LastPage.js"></script>
|
|
130
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSelect.js"></script>
|
|
131
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSize.js"></script>
|
|
132
|
+
<script type="module" src="/kempo-ui/components/tableControls/DeleteRecord.js"></script>
|
|
133
|
+
<script type="module" src="/kempo-ui/components/tableControls/DeleteSelected.js"></script>
|
|
134
|
+
<script type="module" src="/kempo-ui/components/Icon.js"></script>
|
|
135
|
+
<script type="module" src="/admin/components/TableRowLink.js"></script>
|
|
136
|
+
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
137
|
+
|
|
138
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
139
|
+
<h1 class="flex mb0">Fragments</h1>
|
|
140
|
+
<k-permission has="system:fragments:create">
|
|
141
|
+
<button id="createFragmentBtn" class="primary">Create Fragment</button>
|
|
142
|
+
</k-permission>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<h2>Custom Fragments</h2>
|
|
146
|
+
<k-table id="customTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No custom fragments have been created yet.">
|
|
147
|
+
<admin-table-row-link slot="before" url-pattern="/admin/content/fragments/edit?fragment={editPath}" field-name="editPath"></admin-table-row-link>
|
|
148
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
149
|
+
<div>
|
|
150
|
+
<k-permission has="system:fragments:delete">
|
|
151
|
+
<k-tc-delete-selected></k-tc-delete-selected>
|
|
152
|
+
</k-permission>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="d-f full" style="align-items: center;">
|
|
156
|
+
<div>
|
|
157
|
+
<k-tc-first-page></k-tc-first-page>
|
|
158
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
159
|
+
<k-tc-next-page></k-tc-next-page>
|
|
160
|
+
<k-tc-last-page></k-tc-last-page>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="flex"></div>
|
|
163
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
164
|
+
<div class="flex"></div>
|
|
165
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
166
|
+
</div>
|
|
167
|
+
</k-table>
|
|
168
|
+
|
|
169
|
+
<h2>Extension Fragments</h2>
|
|
170
|
+
<k-table id="extensionTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No extension fragments installed.">
|
|
171
|
+
<admin-table-row-link slot="before" url-pattern="/admin/content/fragments/edit?fragment={editPath}" field-name="editPath"></admin-table-row-link>
|
|
172
|
+
<div class="d-f full" style="align-items: center;">
|
|
173
|
+
<div>
|
|
174
|
+
<k-tc-first-page></k-tc-first-page>
|
|
175
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
176
|
+
<k-tc-next-page></k-tc-next-page>
|
|
177
|
+
<k-tc-last-page></k-tc-last-page>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="flex"></div>
|
|
180
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
181
|
+
<div class="flex"></div>
|
|
182
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
183
|
+
</div>
|
|
184
|
+
</k-table>
|
|
185
|
+
|
|
186
|
+
<h2>System Fragments</h2>
|
|
187
|
+
<k-table id="systemTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No system fragments found.">
|
|
188
|
+
<admin-table-row-link slot="before" url-pattern="/admin/content/fragments/edit?fragment={editPath}" field-name="editPath"></admin-table-row-link>
|
|
189
|
+
<div class="d-f full" style="align-items: center;">
|
|
190
|
+
<div>
|
|
191
|
+
<k-tc-first-page></k-tc-first-page>
|
|
192
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
193
|
+
<k-tc-next-page></k-tc-next-page>
|
|
194
|
+
<k-tc-last-page></k-tc-last-page>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="flex"></div>
|
|
197
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
198
|
+
<div class="flex"></div>
|
|
199
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
200
|
+
</div>
|
|
201
|
+
</k-table>
|
|
202
|
+
|
|
203
|
+
<script type="module">
|
|
204
|
+
import { listFragments, listDirectories, createFragment, deleteFragments, getAllCurrentUserPermissions } from '/kempo/sdk.js';
|
|
205
|
+
import { html } from '/kempo-ui/lit-all.min.js';
|
|
206
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
207
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
208
|
+
|
|
209
|
+
await window.customElements.whenDefined('k-table');
|
|
210
|
+
|
|
211
|
+
const [, permData] = await getAllCurrentUserPermissions();
|
|
212
|
+
const myPerms = new Set(permData?.permissions ?? []);
|
|
213
|
+
|
|
214
|
+
const fields = [
|
|
215
|
+
{ name: 'lockedBadge', label: '', size: 40, formatter: v => v },
|
|
216
|
+
{ name: 'name', label: 'Name', size: 200 },
|
|
217
|
+
{ name: 'file', label: 'File', size: 300 },
|
|
218
|
+
{ name: 'author', label: 'Author', size: 150 },
|
|
219
|
+
{ name: 'updatedAt', label: 'Updated', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' },
|
|
220
|
+
{ name: 'createdAt', label: 'Created', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' }
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
const extensionFields = [
|
|
224
|
+
{ name: 'owner', label: 'Extension', size: 120 },
|
|
225
|
+
...fields
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const $customTable = document.getElementById('customTable');
|
|
229
|
+
const $extensionTable = document.getElementById('extensionTable');
|
|
230
|
+
const $systemTable = document.getElementById('systemTable');
|
|
231
|
+
|
|
232
|
+
const loadFragments = async () => {
|
|
233
|
+
const [error, data] = await listFragments();
|
|
234
|
+
if(error){
|
|
235
|
+
Toast.error(error.msg || 'Failed to load fragments');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const toEditPath = f => ({ ...f, editPath: f.file.replace(/\.fragment\.html$/, ''), lockedBadge: f.locked ? '<k-icon name="lock"></k-icon>' : '' });
|
|
239
|
+
const custom = data.fragments.filter(f => f.owner === 'custom').map(toEditPath);
|
|
240
|
+
const extension = data.fragments.filter(f => f.owner !== 'custom' && f.owner !== 'system').map(toEditPath);
|
|
241
|
+
const system = data.fragments.filter(f => f.owner === 'system').map(toEditPath);
|
|
242
|
+
$customTable.setData({ records: custom, fields });
|
|
243
|
+
$extensionTable.setData({ records: extension, fields: extensionFields });
|
|
244
|
+
$systemTable.setData({ records: system, fields });
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if(myPerms.has('system:fragments:delete')){
|
|
248
|
+
$customTable.setAttribute('request-delete', '');
|
|
249
|
+
$customTable.setAttribute('enable-selection', '');
|
|
250
|
+
const del = document.createElement('k-tc-delete-record');
|
|
251
|
+
del.setAttribute('slot', 'after');
|
|
252
|
+
$customTable.appendChild(del);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await loadFragments();
|
|
256
|
+
|
|
257
|
+
$customTable.addEventListener('requestDelete', ({ detail: { records, approve, reject } }) => {
|
|
258
|
+
const locked = records.filter(r => r.locked);
|
|
259
|
+
if(locked.length){
|
|
260
|
+
Toast.error(locked.length === 1 ? `"${locked[0].name || locked[0].file}" is locked and cannot be deleted.` : `${locked.length} selected fragments are locked and cannot be deleted.`);
|
|
261
|
+
reject();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const count = records.length;
|
|
265
|
+
const msg = count === 1
|
|
266
|
+
? `Delete "${records[0].name || records[0].file}"? This action cannot be undone.`
|
|
267
|
+
: `Delete ${count} fragments? This action cannot be undone.`;
|
|
268
|
+
Dialog.confirm(msg, async (confirmed) => {
|
|
269
|
+
if(!confirmed){ reject(); return; }
|
|
270
|
+
const files = records.map(r => r.file);
|
|
271
|
+
const [error] = await deleteFragments(files);
|
|
272
|
+
if(error){
|
|
273
|
+
reject();
|
|
274
|
+
Toast.error(error.msg || 'Failed to delete');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
approve();
|
|
278
|
+
Toast.success(count === 1 ? 'Fragment deleted' : `${count} fragments deleted`);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/*
|
|
283
|
+
Create Fragment Dialog
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
const slugify = name => name
|
|
287
|
+
.toLowerCase()
|
|
288
|
+
.trim()
|
|
289
|
+
.replace(/[^\w\s-]/g, '')
|
|
290
|
+
.replace(/[\s_]+/g, '-')
|
|
291
|
+
.replace(/-+/g, '-')
|
|
292
|
+
.replace(/^-|-$/g, '');
|
|
293
|
+
|
|
294
|
+
document.getElementById('createFragmentBtn')?.addEventListener('click', async () => {
|
|
295
|
+
const [dirError, dirData] = await listDirectories();
|
|
296
|
+
if(dirError){
|
|
297
|
+
Toast.error(dirError.msg || 'Failed to load directories');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const directories = dirData.directories;
|
|
302
|
+
const isValidDir = p => /^\/[a-zA-Z0-9_\-\[\]\/]*$/.test(p) && !p.includes('..') && !p.includes('//');
|
|
303
|
+
const dirToInternal = p => { const t = (p || '/').replace(/^\//, '').replace(/\/$/, ''); return t || '.'; };
|
|
304
|
+
|
|
305
|
+
const $dialog = Dialog.create(html`
|
|
306
|
+
<div class="p">
|
|
307
|
+
<div class="mb">
|
|
308
|
+
<label class="d-b mb-sm">Location</label>
|
|
309
|
+
<input type="text" id="dlg-directory" class="full" list="dlg-dir-list" value="/" placeholder="/">
|
|
310
|
+
<datalist id="dlg-dir-list">
|
|
311
|
+
${directories.map(d => html`<option value="${d === '.' ? '/' : '/' + d}">`)}
|
|
312
|
+
</datalist>
|
|
313
|
+
<p id="dlg-dir-error" class="mt-sm tc-danger d-n"><small>Invalid path — use format: /path/to/directory</small></p>
|
|
314
|
+
</div>
|
|
315
|
+
<div>
|
|
316
|
+
<label class="d-b mb-sm">Fragment Name</label>
|
|
317
|
+
<input type="text" id="dlg-name" class="full" placeholder="My Fragment" required>
|
|
318
|
+
<p class="mt-sm muted"><small id="dlg-slug"></small></p>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
`, {
|
|
322
|
+
title: 'Create Fragment',
|
|
323
|
+
confirmText: 'Create',
|
|
324
|
+
confirmAction: async () => {
|
|
325
|
+
const name = $dialog.querySelector('#dlg-name').value.trim();
|
|
326
|
+
const dirInput = $dialog.querySelector('#dlg-directory').value.trim() || '/';
|
|
327
|
+
|
|
328
|
+
if(!name){
|
|
329
|
+
Toast.error('Fragment name is required');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if(!isValidDir(dirInput)){
|
|
334
|
+
Toast.error('Invalid directory path');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const directory = dirToInternal(dirInput);
|
|
339
|
+
|
|
340
|
+
const [error, data] = await createFragment({
|
|
341
|
+
directory: directory === '.' ? '' : directory,
|
|
342
|
+
name
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if(error){
|
|
346
|
+
Toast.error(error.msg || 'Failed to create fragment');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
Toast.success('Fragment created — redirecting to editor...');
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
window.location.href = `/admin/content/fragments/edit?fragment=${encodeURIComponent(data.file.replace(/\.fragment\.html$/, ''))}`;
|
|
353
|
+
}, 1500);
|
|
354
|
+
},
|
|
355
|
+
cancelText: 'Cancel'
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const updateSlug = () => {
|
|
359
|
+
const slug = slugify($dialog.querySelector('#dlg-name').value);
|
|
360
|
+
const dir = dirToInternal($dialog.querySelector('#dlg-directory').value);
|
|
361
|
+
const prefix = dir === '.' ? '' : dir + '/';
|
|
362
|
+
$dialog.querySelector('#dlg-slug').textContent = slug ? prefix + slug + '.fragment.html' : '';
|
|
363
|
+
};
|
|
364
|
+
$dialog.querySelector('#dlg-directory').addEventListener('input', e => {
|
|
365
|
+
$dialog.querySelector('#dlg-dir-error').classList.toggle('d-n', isValidDir(e.target.value || '/'));
|
|
366
|
+
updateSlug();
|
|
367
|
+
});
|
|
368
|
+
$dialog.querySelector('#dlg-name').addEventListener('input', updateSlug);
|
|
369
|
+
});
|
|
370
|
+
</script>
|
|
371
|
+
|
|
372
|
+
</k-main>
|
|
373
|
+
|
|
374
|
+
</body>
|
|
375
|
+
</html>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
owner: system
|
|
3
|
+
locked: true
|
|
4
|
+
name:
|
|
5
|
+
author:
|
|
6
|
+
created:
|
|
7
|
+
updated:
|
|
8
|
+
-->
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<link rel="icon" type="image/png" sizes="48x48" href="/media/icon48.png">
|
|
15
|
+
<link rel="manifest" href="/manifest.json">
|
|
16
|
+
<title>Edit Global Content - Admin</title>
|
|
17
|
+
<link rel="stylesheet" href="/kempo-css/kempo.min.css">
|
|
18
|
+
<script src="/admin/init.js"></script>
|
|
19
|
+
|
|
20
|
+
<script type="module" src="/admin/components/GlobalContentEditor.js"></script>
|
|
21
|
+
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<!--
|
|
25
|
+
locked: true
|
|
26
|
+
-->
|
|
27
|
+
|
|
28
|
+
<k-aside
|
|
29
|
+
main="push"
|
|
30
|
+
state="expanded"
|
|
31
|
+
style="--aside_padding: 0;"
|
|
32
|
+
persistent-id="admin-nav"
|
|
33
|
+
>
|
|
34
|
+
<k-aside-toggle>
|
|
35
|
+
<h3 class="m0">Kempo Admin</h3>
|
|
36
|
+
</k-aside-toggle>
|
|
37
|
+
<k-aside-item
|
|
38
|
+
icon="preview"
|
|
39
|
+
href="/"
|
|
40
|
+
no-expand
|
|
41
|
+
>Site</k-aside-item>
|
|
42
|
+
<k-aside-item
|
|
43
|
+
icon="cards"
|
|
44
|
+
href="/admin"
|
|
45
|
+
no-expand
|
|
46
|
+
>Dashboard</k-aside-item>
|
|
47
|
+
<k-aside-menu
|
|
48
|
+
icon="manage-accounts"
|
|
49
|
+
label="Accounts"
|
|
50
|
+
>
|
|
51
|
+
<k-aside-item
|
|
52
|
+
icon="person"
|
|
53
|
+
href="/admin/accounts/users"
|
|
54
|
+
no-expand
|
|
55
|
+
>Users</k-aside-item>
|
|
56
|
+
<k-aside-item
|
|
57
|
+
icon="group"
|
|
58
|
+
href="/admin/accounts/groups"
|
|
59
|
+
no-expand
|
|
60
|
+
>Groups</k-aside-item>
|
|
61
|
+
<k-aside-item
|
|
62
|
+
icon="lock"
|
|
63
|
+
href="/admin/accounts/permissions"
|
|
64
|
+
no-expand
|
|
65
|
+
>Permissions</k-aside-item>
|
|
66
|
+
</k-aside-menu>
|
|
67
|
+
<k-aside-menu
|
|
68
|
+
icon="cards_stack"
|
|
69
|
+
label="Content"
|
|
70
|
+
>
|
|
71
|
+
<k-aside-item
|
|
72
|
+
icon="browse"
|
|
73
|
+
href="/admin/content/templates"
|
|
74
|
+
>Templates</k-aside-item>
|
|
75
|
+
<k-aside-item
|
|
76
|
+
icon="docs"
|
|
77
|
+
href="/admin/content/pages"
|
|
78
|
+
>Pages</k-aside-item>
|
|
79
|
+
<k-aside-item
|
|
80
|
+
icon="content"
|
|
81
|
+
href="/admin/content/fragments"
|
|
82
|
+
>Fragments</k-aside-item>
|
|
83
|
+
<k-aside-item
|
|
84
|
+
icon="globe"
|
|
85
|
+
href="/admin/content/globals"
|
|
86
|
+
>Global Content</k-aside-item>
|
|
87
|
+
</k-aside-menu>
|
|
88
|
+
<k-aside-item
|
|
89
|
+
icon="settings"
|
|
90
|
+
href="/admin/settings"
|
|
91
|
+
no-expand
|
|
92
|
+
>Settings</k-aside-item>
|
|
93
|
+
<k-aside-item
|
|
94
|
+
icon="extension"
|
|
95
|
+
href="/admin/extensions"
|
|
96
|
+
no-expand
|
|
97
|
+
>Extensions</k-aside-item>
|
|
98
|
+
<hr>
|
|
99
|
+
<k-aside-spacer></k-aside-spacer>
|
|
100
|
+
<k-theme-switcher
|
|
101
|
+
labels
|
|
102
|
+
style="--padding:var(--spacer_h);margin:var(--spacer_h) auto"
|
|
103
|
+
></k-theme-switcher>
|
|
104
|
+
</k-aside>
|
|
105
|
+
<script type="module">
|
|
106
|
+
import '/kempo-ui/components/ThemeSwitcher.js';
|
|
107
|
+
const items = document.querySelectorAll('k-aside-item');
|
|
108
|
+
const currentPath = window.location.pathname;
|
|
109
|
+
|
|
110
|
+
items.forEach(item => {
|
|
111
|
+
const href = item.getAttribute('href');
|
|
112
|
+
if (!href) return;
|
|
113
|
+
const isExact = href === '/admin' || href === '/';
|
|
114
|
+
const match = isExact
|
|
115
|
+
? (currentPath === href || currentPath === href + '/')
|
|
116
|
+
: (currentPath === href || currentPath.startsWith(href + '/'));
|
|
117
|
+
if (match) {
|
|
118
|
+
item.setAttribute('active', '');
|
|
119
|
+
const menu = item.closest('k-aside-menu');
|
|
120
|
+
if (menu) menu.setAttribute('open', '');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<k-main>
|
|
126
|
+
|
|
127
|
+
<admin-global-content-editor></admin-global-content-editor>
|
|
128
|
+
|
|
129
|
+
</k-main>
|
|
130
|
+
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|