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,429 @@
|
|
|
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>Templates - 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">Templates</h1>
|
|
140
|
+
<k-permission has="system:pages:create">
|
|
141
|
+
<button id="createTemplateBtn" class="primary">Create Template</button>
|
|
142
|
+
</k-permission>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<h2>Custom Templates</h2>
|
|
146
|
+
<k-table
|
|
147
|
+
id="customTable"
|
|
148
|
+
enable-pages
|
|
149
|
+
page-size="25"
|
|
150
|
+
page-size-options="[10,25,50,100]"
|
|
151
|
+
placeholder="No custom templates have been created yet."
|
|
152
|
+
>
|
|
153
|
+
<admin-table-row-link
|
|
154
|
+
slot="before"
|
|
155
|
+
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
156
|
+
field-name="editPath"
|
|
157
|
+
></admin-table-row-link>
|
|
158
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
159
|
+
<div>
|
|
160
|
+
<k-permission has="system:pages:delete">
|
|
161
|
+
<k-tc-delete-selected></k-tc-delete-selected>
|
|
162
|
+
</k-permission>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="d-f full" style="align-items: center;">
|
|
166
|
+
<div>
|
|
167
|
+
<k-tc-first-page></k-tc-first-page>
|
|
168
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
169
|
+
<k-tc-next-page></k-tc-next-page>
|
|
170
|
+
<k-tc-last-page></k-tc-last-page>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="flex"></div>
|
|
173
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
174
|
+
<div class="flex"></div>
|
|
175
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
176
|
+
</div>
|
|
177
|
+
</k-table>
|
|
178
|
+
|
|
179
|
+
<h2>Extension Templates</h2>
|
|
180
|
+
<k-table
|
|
181
|
+
id="extensionTable"
|
|
182
|
+
enable-pages
|
|
183
|
+
page-size="25"
|
|
184
|
+
page-size-options="[10,25,50,100]"
|
|
185
|
+
placeholder="No extension templates installed."
|
|
186
|
+
>
|
|
187
|
+
<admin-table-row-link
|
|
188
|
+
slot="before"
|
|
189
|
+
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
190
|
+
field-name="editPath"
|
|
191
|
+
></admin-table-row-link>
|
|
192
|
+
<div class="d-f full" style="align-items: center;">
|
|
193
|
+
<div>
|
|
194
|
+
<k-tc-first-page></k-tc-first-page>
|
|
195
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
196
|
+
<k-tc-next-page></k-tc-next-page>
|
|
197
|
+
<k-tc-last-page></k-tc-last-page>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="flex"></div>
|
|
200
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
201
|
+
<div class="flex"></div>
|
|
202
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
203
|
+
</div>
|
|
204
|
+
</k-table>
|
|
205
|
+
|
|
206
|
+
<h2>System Templates</h2>
|
|
207
|
+
<k-table
|
|
208
|
+
id="systemTable"
|
|
209
|
+
enable-pages
|
|
210
|
+
page-size="25"
|
|
211
|
+
page-size-options="[10,25,50,100]"
|
|
212
|
+
placeholder="No system templates found."
|
|
213
|
+
>
|
|
214
|
+
<admin-table-row-link
|
|
215
|
+
slot="before"
|
|
216
|
+
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
217
|
+
field-name="editPath"
|
|
218
|
+
></admin-table-row-link>
|
|
219
|
+
<div class="d-f full" style="align-items: center;">
|
|
220
|
+
<div>
|
|
221
|
+
<k-tc-first-page></k-tc-first-page>
|
|
222
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
223
|
+
<k-tc-next-page></k-tc-next-page>
|
|
224
|
+
<k-tc-last-page></k-tc-last-page>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="flex"></div>
|
|
227
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
228
|
+
<div class="flex"></div>
|
|
229
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
230
|
+
</div>
|
|
231
|
+
</k-table>
|
|
232
|
+
|
|
233
|
+
<script type="module">
|
|
234
|
+
import { listTemplates, listDirectories, createTemplate, deleteTemplates, getAllCurrentUserPermissions } from '/kempo/sdk.js';
|
|
235
|
+
import { html } from '/kempo-ui/lit-all.min.js';
|
|
236
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
237
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
238
|
+
|
|
239
|
+
await window.customElements.whenDefined('k-table');
|
|
240
|
+
|
|
241
|
+
const [, permData] = await getAllCurrentUserPermissions();
|
|
242
|
+
const myPerms = new Set(permData?.permissions ?? []);
|
|
243
|
+
|
|
244
|
+
const fields = [
|
|
245
|
+
{ name: 'lockedBadge', label: '', size: 40, formatter: v => v },
|
|
246
|
+
{ name: 'name', label: 'Name', size: 150 },
|
|
247
|
+
{ name: 'directory', label: 'Directory', size: 200 },
|
|
248
|
+
{ name: 'author', label: 'Author', size: 150 },
|
|
249
|
+
{ name: 'updatedAt', label: 'Updated', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' },
|
|
250
|
+
{ name: 'createdAt', label: 'Created', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' }
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const extensionFields = [
|
|
254
|
+
{ name: 'owner', label: 'Extension', size: 120 },
|
|
255
|
+
...fields
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const $customTable = document.getElementById('customTable');
|
|
259
|
+
const $extensionTable = document.getElementById('extensionTable');
|
|
260
|
+
const $systemTable = document.getElementById('systemTable');
|
|
261
|
+
|
|
262
|
+
const loadTemplates = async () => {
|
|
263
|
+
const [error, data] = await listTemplates();
|
|
264
|
+
if(error){
|
|
265
|
+
Toast.error(error.msg || 'Failed to load templates');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const toEditPath = t => ({
|
|
269
|
+
...t,
|
|
270
|
+
directory: t.directory === '.' ? '/' : t.directory,
|
|
271
|
+
editPath: (t.directory === '.' ? t.name : `${t.directory}/${t.name}`),
|
|
272
|
+
lockedBadge: t.locked ? '<k-icon name="lock"></k-icon>' : ''
|
|
273
|
+
});
|
|
274
|
+
const custom = data.templates.filter(t => t.owner === 'custom').map(toEditPath);
|
|
275
|
+
const extension = data.templates.filter(t => t.owner !== 'custom' && t.owner !== 'system').map(toEditPath);
|
|
276
|
+
const system = data.templates.filter(t => t.owner === 'system').map(toEditPath);
|
|
277
|
+
$customTable.setData({ records: custom, fields });
|
|
278
|
+
$extensionTable.setData({ records: extension, fields: extensionFields });
|
|
279
|
+
$systemTable.setData({ records: system, fields });
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if(myPerms.has('system:pages:delete')){
|
|
283
|
+
$customTable.setAttribute('request-delete', '');
|
|
284
|
+
$customTable.setAttribute('enable-selection', '');
|
|
285
|
+
const del = document.createElement('k-tc-delete-record');
|
|
286
|
+
del.setAttribute('slot', 'after');
|
|
287
|
+
$customTable.appendChild(del);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await loadTemplates();
|
|
291
|
+
|
|
292
|
+
$customTable.addEventListener('requestDelete', ({ detail: { records, approve, reject } }) => {
|
|
293
|
+
const locked = records.filter(r => r.locked);
|
|
294
|
+
if(locked.length){
|
|
295
|
+
Toast.error(locked.length === 1 ? `"${locked[0].name}" is locked and cannot be deleted.` : `${locked.length} selected templates are locked and cannot be deleted.`);
|
|
296
|
+
reject();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const count = records.length;
|
|
300
|
+
const msg = count === 1
|
|
301
|
+
? `Delete "${records[0].name}"? This action cannot be undone.`
|
|
302
|
+
: `Delete ${count} templates? This action cannot be undone.`;
|
|
303
|
+
Dialog.confirm(msg, async (confirmed) => {
|
|
304
|
+
if(!confirmed){ reject(); return; }
|
|
305
|
+
const files = records.map(r => r.path);
|
|
306
|
+
const [error] = await deleteTemplates(files);
|
|
307
|
+
if(error){
|
|
308
|
+
reject();
|
|
309
|
+
Toast.error(error.msg || 'Failed to delete');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
approve();
|
|
313
|
+
Toast.success(count === 1 ? 'Template deleted' : `${count} templates deleted`);
|
|
314
|
+
await loadTemplates();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
Create Template Dialog
|
|
320
|
+
*/
|
|
321
|
+
|
|
322
|
+
const slugify = name => name
|
|
323
|
+
.toLowerCase()
|
|
324
|
+
.trim()
|
|
325
|
+
.replace(/[^\w\s-]/g, '')
|
|
326
|
+
.replace(/[\s_]+/g, '-')
|
|
327
|
+
.replace(/-+/g, '-')
|
|
328
|
+
.replace(/^-|-$/g, '');
|
|
329
|
+
|
|
330
|
+
document.getElementById('createTemplateBtn')?.addEventListener('click', async () => {
|
|
331
|
+
const [[dirError, dirData], [tplError, tplData]] = await Promise.all([
|
|
332
|
+
listDirectories(),
|
|
333
|
+
listTemplates()
|
|
334
|
+
]);
|
|
335
|
+
if(dirError){
|
|
336
|
+
Toast.error(dirError.msg || 'Failed to load directories');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const directories = dirData.directories;
|
|
341
|
+
const allTemplates = tplError ? [] : (tplData.templates || []);
|
|
342
|
+
const templateOptions = allTemplates.map(t => {
|
|
343
|
+
const path = t.directory === '.' ? t.name : `${t.directory}/${t.name}`;
|
|
344
|
+
return { path: t.path, label: '/' + path.replace(/\.template\.html$/, '') };
|
|
345
|
+
});
|
|
346
|
+
const isValidDir = p => /^\/[a-zA-Z0-9_\-\[\]\/]*$/.test(p) && !p.includes('..') && !p.includes('//');
|
|
347
|
+
const dirToInternal = p => { const t = (p || '/').replace(/^\//, '').replace(/\/$/, ''); return t || '.'; };
|
|
348
|
+
|
|
349
|
+
const $dialog = Dialog.create(html`
|
|
350
|
+
<div class="p">
|
|
351
|
+
<div class="mb">
|
|
352
|
+
<label class="d-b mb-sm">Copy From</label>
|
|
353
|
+
<select id="dlg-copy-from" class="full">
|
|
354
|
+
<option value="">Blank template</option>
|
|
355
|
+
${templateOptions.map(t => html`<option value="${t.path}">${t.label}</option>`)}
|
|
356
|
+
</select>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="mb">
|
|
359
|
+
<label class="d-b mb-sm">Location</label>
|
|
360
|
+
<input type="text" id="dlg-directory" class="full" list="dlg-dir-list" value="/" placeholder="/">
|
|
361
|
+
<datalist id="dlg-dir-list">
|
|
362
|
+
${directories.map(d => html`<option value="${d === '.' ? '/' : '/' + d}">`)}
|
|
363
|
+
</datalist>
|
|
364
|
+
<p id="dlg-dir-error" class="mt-sm tc-danger d-n"><small>Invalid path — use format: /path/to/directory</small></p>
|
|
365
|
+
</div>
|
|
366
|
+
<div>
|
|
367
|
+
<label class="d-b mb-sm">Template Name</label>
|
|
368
|
+
<input type="text" id="dlg-name" class="full" placeholder="My Template" required>
|
|
369
|
+
<p class="mt-sm muted"><small id="dlg-slug"></small></p>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
`, {
|
|
373
|
+
title: 'Create Template',
|
|
374
|
+
confirmText: 'Create',
|
|
375
|
+
confirmAction: async () => {
|
|
376
|
+
const name = $dialog.querySelector('#dlg-name').value.trim();
|
|
377
|
+
const dirInput = $dialog.querySelector('#dlg-directory').value.trim() || '/';
|
|
378
|
+
|
|
379
|
+
if(!name){
|
|
380
|
+
Toast.error('Template name is required');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if(!isValidDir(dirInput)){
|
|
385
|
+
Toast.error('Invalid directory path');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const directory = dirToInternal(dirInput);
|
|
390
|
+
const copyFrom = $dialog.querySelector('#dlg-copy-from').value || undefined;
|
|
391
|
+
|
|
392
|
+
const [error, data] = await createTemplate({
|
|
393
|
+
directory: directory === '.' ? '' : directory,
|
|
394
|
+
name,
|
|
395
|
+
copyFrom
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if(error){
|
|
399
|
+
Toast.error(error.msg || 'Failed to create template');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
Toast.success('Template created — redirecting to editor...');
|
|
404
|
+
setTimeout(() => {
|
|
405
|
+
const editPath = data.file.replace(/\.template\.html$/, '');
|
|
406
|
+
window.location.href = `/admin/content/templates/edit?template=${encodeURIComponent(editPath)}`;
|
|
407
|
+
}, 1500);
|
|
408
|
+
},
|
|
409
|
+
cancelText: 'Cancel'
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const updateSlug = () => {
|
|
413
|
+
const slug = slugify($dialog.querySelector('#dlg-name').value);
|
|
414
|
+
const dir = dirToInternal($dialog.querySelector('#dlg-directory').value);
|
|
415
|
+
const prefix = dir === '.' ? '/' : '/' + dir + '/';
|
|
416
|
+
$dialog.querySelector('#dlg-slug').textContent = slug ? `${prefix}${slug}.template.html` : '';
|
|
417
|
+
};
|
|
418
|
+
$dialog.querySelector('#dlg-directory').addEventListener('input', e => {
|
|
419
|
+
$dialog.querySelector('#dlg-dir-error').classList.toggle('d-n', isValidDir(e.target.value || '/'));
|
|
420
|
+
updateSlug();
|
|
421
|
+
});
|
|
422
|
+
$dialog.querySelector('#dlg-name').addEventListener('input', updateSlug);
|
|
423
|
+
});
|
|
424
|
+
</script>
|
|
425
|
+
|
|
426
|
+
</k-main>
|
|
427
|
+
|
|
428
|
+
</body>
|
|
429
|
+
</html>
|
|
@@ -0,0 +1,222 @@
|
|
|
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>Extensions - 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="/admin/components/tableControls/InstallExtension.js"></script>
|
|
133
|
+
<script type="module" src="/admin/components/tableControls/UninstallExtension.js"></script>
|
|
134
|
+
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
135
|
+
|
|
136
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
137
|
+
<h1 class="flex mb0">Extensions</h1>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<h2>Installed Extensions</h2>
|
|
141
|
+
<k-table id="installedTable" enable-pages page-size="25" placeholder="No extensions are currently installed.">
|
|
142
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
143
|
+
<div>
|
|
144
|
+
<k-permission has="system:extensions:uninstall">
|
|
145
|
+
<admin-uninstall-extension></admin-uninstall-extension>
|
|
146
|
+
</k-permission>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="d-f full" style="align-items: center;">
|
|
150
|
+
<div>
|
|
151
|
+
<k-tc-first-page></k-tc-first-page>
|
|
152
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
153
|
+
<k-tc-next-page></k-tc-next-page>
|
|
154
|
+
<k-tc-last-page></k-tc-last-page>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="flex"></div>
|
|
157
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
158
|
+
<div class="flex"></div>
|
|
159
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
160
|
+
</div>
|
|
161
|
+
</k-table>
|
|
162
|
+
|
|
163
|
+
<h2>Available Extensions</h2>
|
|
164
|
+
<k-table id="availableTable" enable-pages page-size="25" placeholder="No extensions are available to install. Install a kempo extension package via npm first.">
|
|
165
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
166
|
+
<div>
|
|
167
|
+
<k-permission has="system:extensions:install">
|
|
168
|
+
<admin-install-extension></admin-install-extension>
|
|
169
|
+
</k-permission>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
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
|
+
|
|
187
|
+
</k-main>
|
|
188
|
+
|
|
189
|
+
<script type="module">
|
|
190
|
+
import sdk from '/kempo/sdk.js';
|
|
191
|
+
|
|
192
|
+
const installedTable = document.getElementById('installedTable');
|
|
193
|
+
const availableTable = document.getElementById('availableTable');
|
|
194
|
+
|
|
195
|
+
const [installedErr, installedData] = await sdk.listExtensions();
|
|
196
|
+
if(!installedErr){
|
|
197
|
+
installedTable.setData(installedData.items, {
|
|
198
|
+
columns: [
|
|
199
|
+
{ key: 'name', label: 'Name' },
|
|
200
|
+
{ key: 'version', label: 'Version' },
|
|
201
|
+
{ key: 'description', label: 'Description' },
|
|
202
|
+
{ key: 'enabled', label: 'Enabled' },
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const [availableErr, availableData] = await sdk.listAvailableExtensions();
|
|
208
|
+
if(!availableErr){
|
|
209
|
+
const installedNames = new Set((installedData?.items || []).map(e => e.name));
|
|
210
|
+
const notInstalled = availableData.items.filter(e => !installedNames.has(e.name));
|
|
211
|
+
availableTable.setData(notInstalled, {
|
|
212
|
+
columns: [
|
|
213
|
+
{ key: 'name', label: 'Name' },
|
|
214
|
+
{ key: 'version', label: 'Version' },
|
|
215
|
+
{ key: 'description', label: 'Description' },
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
</body>
|
|
222
|
+
</html>
|