domma-cms 0.74.0 → 0.76.0
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/CLAUDE.md +3 -1
- package/admin/js/app.js +4 -4
- package/admin/js/lib/actions-arrange.js +1 -0
- package/admin/js/lib/api-endpoints-arrange.js +1 -0
- package/admin/js/lib/api-tokens-arrange.js +3 -0
- package/admin/js/lib/blocks-arrange.js +1 -0
- package/admin/js/lib/collections-arrange.js +1 -0
- package/admin/js/lib/components-arrange.js +1 -0
- package/admin/js/lib/context-menus-arrange.js +1 -0
- package/admin/js/lib/effects-arrange.js +3 -0
- package/admin/js/lib/forms-arrange.js +1 -0
- package/admin/js/lib/layouts-arrange.js +1 -0
- package/admin/js/lib/media-arrange.js +1 -0
- package/admin/js/lib/menus-arrange.js +1 -0
- package/admin/js/lib/my-profile-arrange.js +1 -0
- package/admin/js/lib/notifications-arrange.js +1 -0
- package/admin/js/lib/pages-arrange.js +1 -0
- package/admin/js/lib/plugins-arrange.js +2 -0
- package/admin/js/lib/roles-arrange.js +1 -0
- package/admin/js/lib/search-settings-arrange.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +3 -3
- package/admin/js/lib/site-settings-arrange.js +1 -0
- package/admin/js/lib/theme-settings-arrange.js +1 -0
- package/admin/js/lib/users-arrange.js +1 -0
- package/admin/js/lib/views-arrange.js +1 -0
- package/admin/js/templates/actions-list.html +73 -14
- package/admin/js/templates/api-endpoints.html +63 -10
- package/admin/js/templates/api-tokens.html +83 -10
- package/admin/js/templates/blocks.html +76 -15
- package/admin/js/templates/collections.html +108 -52
- package/admin/js/templates/components.html +82 -15
- package/admin/js/templates/context-menus.html +55 -13
- package/admin/js/templates/effects.html +155 -72
- package/admin/js/templates/forms.html +67 -14
- package/admin/js/templates/layouts.html +126 -43
- package/admin/js/templates/media.html +99 -13
- package/admin/js/templates/menus.html +57 -11
- package/admin/js/templates/my-profile.html +100 -12
- package/admin/js/templates/notifications.html +70 -10
- package/admin/js/templates/pages.html +78 -23
- package/admin/js/templates/plugins.html +100 -28
- package/admin/js/templates/roles.html +78 -7
- package/admin/js/templates/search.html +193 -216
- package/admin/js/templates/settings.html +404 -472
- package/admin/js/templates/theme.html +164 -132
- package/admin/js/templates/users.html +69 -9
- package/admin/js/templates/views-list.html +63 -9
- package/admin/js/views/actions-list.js +37 -1
- package/admin/js/views/api-endpoints.js +11 -7
- package/admin/js/views/api-tokens.js +62 -8
- package/admin/js/views/blocks.js +34 -5
- package/admin/js/views/collections.js +36 -1
- package/admin/js/views/components.js +27 -5
- package/admin/js/views/context-menus.js +10 -5
- package/admin/js/views/effects.js +31 -1
- package/admin/js/views/forms.js +57 -1
- package/admin/js/views/layouts.js +81 -1
- package/admin/js/views/media.js +40 -1
- package/admin/js/views/menus.js +11 -5
- package/admin/js/views/my-profile.js +1 -1
- package/admin/js/views/notifications.js +18 -1
- package/admin/js/views/pages.js +28 -8
- package/admin/js/views/plugins.js +97 -39
- package/admin/js/views/roles.js +49 -4
- package/admin/js/views/search.js +7 -1
- package/admin/js/views/settings.js +13 -3
- package/admin/js/views/theme.js +55 -1
- package/admin/js/views/users.js +13 -4
- package/admin/js/views/views-list.js +11 -1
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +97 -40
- package/plugins/_lib/admin/mail/contacts.js +4 -1
- package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
- package/plugins/_lib/admin/mail/folder-tree.js +1 -1
- package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
- package/plugins/_lib/admin/mail/mail.css +1 -1
- package/plugins/_lib/admin/mail/mail.html +1 -1
- package/plugins/_lib/admin/mail/reader-view.js +303 -44
- package/plugins/_lib/admin/mail/when-closed.js +26 -0
- package/plugins/_lib/mail/readRoutes.js +43 -1
- package/plugins/_lib/mail/unreadBadge.js +42 -0
- package/plugins/free-tier.lock.json +5 -5
- package/plugins/mail-reader/CLAUDE.md +1 -0
- package/plugins/mail-reader/plugin.js +3 -1
- package/plugins/mail-reader/plugin.json +5 -4
- package/server/routes/api/actions.js +64 -2
- package/server/routes/api/api-endpoints.js +41 -0
- package/server/routes/api/api-tokens.js +56 -0
- package/server/routes/api/auth.js +7 -1
- package/server/routes/api/blocks.js +170 -8
- package/server/routes/api/collections.js +66 -2
- package/server/routes/api/components.js +46 -0
- package/server/routes/api/context-menus.js +8 -0
- package/server/routes/api/effects.js +68 -13
- package/server/routes/api/forms.js +123 -10
- package/server/routes/api/layouts.js +93 -2
- package/server/routes/api/media.js +56 -1
- package/server/routes/api/menus.js +53 -6
- package/server/routes/api/notifications.js +140 -14
- package/server/routes/api/pages.js +46 -0
- package/server/routes/api/plugins.js +135 -4
- package/server/routes/api/roles.js +111 -0
- package/server/routes/api/search.js +38 -4
- package/server/routes/api/settings.js +52 -0
- package/server/routes/api/theme.js +55 -0
- package/server/routes/api/users.js +53 -1
- package/server/routes/api/views.js +27 -3
- package/server/server.js +28 -1
- package/server/services/contextMenus.js +41 -0
- package/server/services/plugins.js +7 -0
- package/server/services/roles.js +3 -0
- package/server/services/search.js +58 -0
- package/server/services/users.js +14 -0
- package/server/services/views.js +38 -0
- package/admin/js/templates/plugin-marketplace.html +0 -29
- package/admin/js/views/plugin-marketplace.js +0 -1
|
@@ -1,19 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<!-- Actions. Bound with M.applyBindings - see admin/views/actions-list.js, which also inserts the row
|
|
2
|
+
template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
|
|
3
|
+
the Tool banner above is the heading, and New action is a banner action. -->
|
|
4
|
+
<div class="acx">
|
|
5
|
+
<p class="acx-note">Buttons on a collection's entries that run steps - update a field, move or create an entry,
|
|
6
|
+
call a webhook, send an email.
|
|
7
|
+
<span data-help-title="Actions" data-help="An action belongs to one collection and shows as a button on its entries (in the admin's entry list, and on the site with the [transitions] shortcode). Its steps run in order. With a transition set, it only appears while the entry's field has one of its 'from' values, and moves it to 'to'. Roles decide who may run it."></span></p>
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
<div class="card-
|
|
10
|
-
<span data-icon="
|
|
11
|
-
<
|
|
9
|
+
<!-- File mode: actions live in MongoDB. A state of the site, explained, not an error. -->
|
|
10
|
+
<div class="acx-card acx-pro" data-bind-hidden="!needsMongo.value">
|
|
11
|
+
<div class="acx-card-title"><span data-icon="database" data-icon-size="16"></span> Actions need MongoDB</div>
|
|
12
|
+
<p class="acx-note">Actions are a Pro feature: their settings are kept in MongoDB, and this site has no
|
|
13
|
+
MongoDB connection. Add a "default" connection (pro mode) and they appear here. Nothing else on the site
|
|
14
|
+
is affected.</p>
|
|
12
15
|
</div>
|
|
13
|
-
</div>
|
|
14
16
|
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
<p class="acx-notice" data-bind-hidden="!notice.value">
|
|
18
|
+
<span data-bind-text="notice.value"></span> <a href="#/actions">Show all actions</a></p>
|
|
19
|
+
|
|
20
|
+
<div class="acx-card" data-bind-hidden="needsMongo.value">
|
|
21
|
+
<div class="acx-bar">
|
|
22
|
+
<input type="search" class="form-input acx-search" placeholder="Search actions…" aria-label="Search actions" data-model="query.value">
|
|
23
|
+
<select class="form-input acx-filter" aria-label="Collection" data-model="collection.value" data-bind-hidden="!collections.value.length">
|
|
24
|
+
<option value="">All collections</option>
|
|
25
|
+
</select>
|
|
26
|
+
<span class="acx-count" data-bind-text="countText.value"></span>
|
|
27
|
+
<span data-help-title="The list" data-help="Click an action to edit it. Right-click it (or ⋮, or Shift+F10) to edit, duplicate, open its collection's entries, copy its slug or delete it."></span>
|
|
28
|
+
</div>
|
|
29
|
+
<p class="acx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
30
|
+
<p class="acx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
31
|
+
<p class="acx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
32
|
+
<div class="acx-list" data-each="rows.value key=slug"></div>
|
|
18
33
|
</div>
|
|
19
34
|
</div>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.acx { display: flex; flex-direction: column; gap: .75rem; }
|
|
38
|
+
.acx [hidden], .acx-form [hidden] { display: none !important; }
|
|
39
|
+
.acx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
40
|
+
.acx-note.is-bad { color: var(--dm-danger); }
|
|
41
|
+
.acx-notice { margin: 0; padding: .5rem .75rem; border-radius: var(--dm-radius-md); font-size: var(--dm-text-sm);
|
|
42
|
+
background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 12%, transparent); color: var(--dm-text); }
|
|
43
|
+
.acx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
44
|
+
.acx-card-title { display: flex; align-items: center; gap: .4rem; font-weight: 600; }
|
|
45
|
+
.acx-pro { border-left: 3px solid var(--dm-warning); }
|
|
46
|
+
.acx-pro .acx-card-title { color: var(--dm-warning); }
|
|
47
|
+
.acx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
48
|
+
.acx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
49
|
+
.acx-filter { width: auto; max-width: 14rem; }
|
|
50
|
+
.acx-count { color: var(--dm-text-muted); font-size: var(--dm-text-sm); margin-left: auto; }
|
|
51
|
+
.acx-list { display: flex; flex-direction: column; }
|
|
52
|
+
.acx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
53
|
+
.acx-row:last-child { border-bottom: 0; }
|
|
54
|
+
.acx-row:hover, .acx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
55
|
+
.acx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0;
|
|
56
|
+
border-radius: var(--dm-radius-md); color: var(--dm-warning); background: color-mix(in srgb, var(--dm-warning) 14%, transparent); }
|
|
57
|
+
.acx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
58
|
+
.acx-main strong, .acx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
59
|
+
.acx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
60
|
+
.acx-chips { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: .3rem; max-width: 55%; }
|
|
61
|
+
.acx-chip { display: inline-flex; align-items: center; gap: .25rem; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem;
|
|
62
|
+
border-radius: 999px; white-space: nowrap; background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
63
|
+
.acx-chip.is-col { background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-primary); font-family: var(--dm-font-mono, monospace); }
|
|
64
|
+
.acx-chip.is-trig { background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-warning); }
|
|
65
|
+
.acx-chip.is-flow { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
66
|
+
.acx-chip.is-warn { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); color: var(--dm-danger); }
|
|
67
|
+
.acx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
68
|
+
.acx-kebab:hover { color: var(--dm-primary); }
|
|
69
|
+
/* The Duplicate slideover */
|
|
70
|
+
.acx-form { display: flex; flex-direction: column; gap: .85rem; }
|
|
71
|
+
.acx-form .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
72
|
+
.acx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
73
|
+
.acx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
74
|
+
.acx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
|
|
75
|
+
.acx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
76
|
+
.acx-spacer { flex: 1; }
|
|
77
|
+
@media (max-width: 720px) { .acx-chips { display: none; } }
|
|
78
|
+
</style>
|
|
@@ -1,13 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
1
|
+
<!-- API Builder list. Bound with M.applyBindings - see admin/views/api-endpoints.js, which also inserts the
|
|
2
|
+
row template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
|
|
3
|
+
the Tool banner above is the heading, and New endpoint is a banner action. -->
|
|
4
|
+
<div class="apx">
|
|
5
|
+
<p class="apx-note">Clean REST URLs over collection data, served at <code>/api/x/<project><path></code>.
|
|
6
|
+
Each endpoint binds a URL to a fixed collection query - definitions are data, never code.
|
|
7
|
+
<span data-help-title="API Builder" data-help="An endpoint answers GET requests at its URL with entries from one collection, filtered by values from the URL (:params) and the query string. Who may call it is set per endpoint: Public (anyone), API token (a token for its project), or a role (signed-in users). Only the fields you list are returned. An endpoint may only read collections in its own project or Core."></span></p>
|
|
8
8
|
|
|
9
|
-
<div class="card">
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<div class="apx-card">
|
|
10
|
+
<div class="apx-bar">
|
|
11
|
+
<div class="apx-seg" role="group" aria-label="Show">
|
|
12
|
+
<button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
|
|
13
|
+
<button type="button" data-on-click="showEnabled" data-bind-class="filter.value === 'enabled' && 'is-on'">Enabled <b data-bind-text="counts.value.enabled"></b></button>
|
|
14
|
+
<button type="button" data-on-click="showDisabled" data-bind-class="filter.value === 'disabled' && 'is-on'">Disabled <b data-bind-text="counts.value.disabled"></b></button>
|
|
15
|
+
<button type="button" data-on-click="showPublic" data-bind-class="filter.value === 'public' && 'is-on'">Public <b data-bind-text="counts.value.public"></b></button>
|
|
16
|
+
</div>
|
|
17
|
+
<select class="form-input apx-project" aria-label="Project" data-model="project.value" data-bind-hidden="projectLocked.value"></select>
|
|
18
|
+
<input type="search" class="form-input apx-search" placeholder="Search endpoints…" aria-label="Search endpoints" data-model="query.value">
|
|
19
|
+
<span data-help-title="The list" data-help="Click an endpoint to open it in the builder, where you can try it. Right-click it (or ⋮, or Shift+F10) to copy its URL, open it in a new tab, enable or disable it, or delete it. A disabled endpoint answers 404 until it is enabled again."></span>
|
|
20
|
+
</div>
|
|
21
|
+
<p class="apx-note apx-scope" data-bind-hidden="!projectLocked.value">
|
|
22
|
+
<span data-bind-text="scopeText.value"></span> <a href="#/api-endpoints">Show all endpoints</a></p>
|
|
23
|
+
<p class="apx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
24
|
+
<p class="apx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
25
|
+
<p class="apx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
26
|
+
<div class="apx-list" data-each="rows.value key=id"></div>
|
|
12
27
|
</div>
|
|
13
28
|
</div>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
.apx { display: flex; flex-direction: column; gap: .75rem; }
|
|
32
|
+
.apx [hidden] { display: none !important; }
|
|
33
|
+
.apx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
34
|
+
.apx-note.is-bad { color: var(--dm-danger); }
|
|
35
|
+
.apx-note code { font-size: .95em; }
|
|
36
|
+
.apx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
37
|
+
.apx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
38
|
+
.apx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
39
|
+
.apx-project { width: auto; min-width: 9rem; }
|
|
40
|
+
.apx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
41
|
+
.apx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
42
|
+
.apx-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
43
|
+
.apx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
|
|
44
|
+
.apx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
45
|
+
.apx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
46
|
+
.apx-list { display: flex; flex-direction: column; }
|
|
47
|
+
.apx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
48
|
+
.apx-row:last-child { border-bottom: 0; }
|
|
49
|
+
.apx-row:hover, .apx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
50
|
+
.apx-verb { flex-shrink: 0; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-xs); font-weight: 700; padding: .15rem .45rem;
|
|
51
|
+
border-radius: var(--dm-radius-sm); color: var(--dm-success); background: color-mix(in srgb, var(--dm-success) 14%, transparent); }
|
|
52
|
+
.apx-row.is-off .apx-verb { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
|
|
53
|
+
.apx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
54
|
+
.apx-main strong, .apx-main code, .apx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
55
|
+
.apx-main code { font-size: var(--dm-text-sm); color: var(--dm-text); background: none; padding: 0; }
|
|
56
|
+
.apx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
57
|
+
.apx-row.is-off .apx-main strong, .apx-row.is-off .apx-main code { color: var(--dm-text-muted); }
|
|
58
|
+
.apx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
|
59
|
+
.apx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
|
|
60
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
61
|
+
.apx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
62
|
+
.apx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
|
|
63
|
+
.apx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
64
|
+
.apx-kebab:hover { color: var(--dm-primary); }
|
|
65
|
+
@media (max-width: 720px) { .apx-chips .apx-chip:not(.is-warn):not(.is-off) { display: none; } }
|
|
66
|
+
</style>
|
|
@@ -1,13 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
1
|
+
<!-- API Tokens. Bound with M.applyBindings - see admin/js/views/api-tokens.js, which also inserts the row
|
|
2
|
+
template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
|
|
3
|
+
the Tool banner above is the heading, and New token is a banner action. -->
|
|
4
|
+
<div class="atx">
|
|
5
|
+
<p class="atx-note">Project-scoped keys for the external collections API (<code>/api/v1/<collection></code>) and API Builder
|
|
6
|
+
endpoints set to token access.
|
|
7
|
+
<span data-help-title="API tokens" data-help="A token works only on collections in its own project, and only where a collection's API access is set to Token. The token itself is shown once, when it is made - only a fingerprint is kept, so a lost token is revoked and a new one made. Scopes narrow a token to some collections and verbs; an expiry date retires it on its own."></span></p>
|
|
8
8
|
|
|
9
|
-
<div class="card">
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<div class="atx-card">
|
|
10
|
+
<div class="atx-bar">
|
|
11
|
+
<div class="atx-seg" role="group" aria-label="Show">
|
|
12
|
+
<button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
|
|
13
|
+
<button type="button" data-on-click="showActive" data-bind-class="filter.value === 'active' && 'is-on'">Active <b data-bind-text="counts.value.active"></b></button>
|
|
14
|
+
<button type="button" data-on-click="showExpiring" data-bind-class="filter.value === 'expiring' && 'is-on'" data-bind-hidden="!counts.value.expiring">Expiring soon <b data-bind-text="counts.value.expiring"></b></button>
|
|
15
|
+
<button type="button" data-on-click="showExpired" data-bind-class="filter.value === 'expired' && 'is-on'" data-bind-hidden="!counts.value.expired">Expired <b data-bind-text="counts.value.expired"></b></button>
|
|
16
|
+
<button type="button" data-on-click="showDisabled" data-bind-class="filter.value === 'disabled' && 'is-on'" data-bind-hidden="!counts.value.disabled">Off <b data-bind-text="counts.value.disabled"></b></button>
|
|
17
|
+
</div>
|
|
18
|
+
<select class="form-input atx-project" aria-label="Project" data-model="project.value" data-bind-hidden="projects.value.length < 2">
|
|
19
|
+
<option value="">All projects</option>
|
|
20
|
+
</select>
|
|
21
|
+
<input type="search" class="form-input atx-search" placeholder="Search name, project, scope…" aria-label="Search tokens" data-model="query.value">
|
|
22
|
+
<span data-help-title="The list" data-help="Tokens that need attention come first: expired ones (every call with them is refused) and ones expiring within two weeks. Click a token to edit its name, scopes or expiry, or switch it off; right-click it (or ⋮, or Shift+F10) to do that, copy its ending, show only its project, or revoke it. A token is never shown again after it is made - only its last four characters."></span>
|
|
23
|
+
</div>
|
|
24
|
+
<p class="atx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
25
|
+
<p class="atx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
26
|
+
<p class="atx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
27
|
+
<div class="atx-list" data-each="rows.value key=id"></div>
|
|
12
28
|
</div>
|
|
13
29
|
</div>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.atx { display: flex; flex-direction: column; gap: .75rem; }
|
|
33
|
+
.atx [hidden], .atx-form [hidden], .atx-secret [hidden] { display: none !important; }
|
|
34
|
+
.atx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
35
|
+
.atx-note.is-bad { color: var(--dm-danger); }
|
|
36
|
+
.atx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
37
|
+
.atx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
38
|
+
.atx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
39
|
+
.atx-project { width: auto; max-width: 14rem; }
|
|
40
|
+
.atx-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
41
|
+
.atx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
42
|
+
.atx-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
43
|
+
.atx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
|
|
44
|
+
.atx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
45
|
+
.atx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
46
|
+
.atx-list { display: flex; flex-direction: column; }
|
|
47
|
+
.atx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
48
|
+
.atx-row:last-child { border-bottom: 0; }
|
|
49
|
+
.atx-row:hover, .atx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
50
|
+
.atx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0;
|
|
51
|
+
border-radius: var(--dm-radius-md); color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
|
|
52
|
+
.atx-row.is-off .atx-icon { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
|
|
53
|
+
.atx-row.is-off .atx-main strong { color: var(--dm-text-muted); }
|
|
54
|
+
.atx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
55
|
+
.atx-main strong, .atx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
56
|
+
.atx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
57
|
+
.atx-main code { font-size: inherit; background: none; padding: 0; color: inherit; }
|
|
58
|
+
.atx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; }
|
|
59
|
+
.atx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
|
|
60
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
61
|
+
.atx-chip:empty { display: none; }
|
|
62
|
+
.atx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
63
|
+
.atx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
64
|
+
.atx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
|
|
65
|
+
.atx-used { min-width: 6.5rem; text-align: right; color: var(--dm-text-muted); font-size: var(--dm-text-xs); white-space: nowrap; }
|
|
66
|
+
.atx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
67
|
+
.atx-kebab:hover { color: var(--dm-primary); }
|
|
68
|
+
/* The New / Edit slideover */
|
|
69
|
+
.atx-form { display: flex; flex-direction: column; gap: .85rem; }
|
|
70
|
+
.atx-form .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
71
|
+
.atx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
72
|
+
.atx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
73
|
+
.atx-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
|
|
74
|
+
.atx-setting .form-label { margin: 0; }
|
|
75
|
+
.atx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
|
|
76
|
+
.atx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
77
|
+
.atx-spacer { flex: 1; }
|
|
78
|
+
.atx-fixed { padding: .45rem .6rem; border: 1px dashed var(--dm-border); border-radius: var(--dm-radius-sm); color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
79
|
+
/* The created token, shown once */
|
|
80
|
+
.atx-secret { display: flex; flex-direction: column; gap: .8rem; }
|
|
81
|
+
.atx-warn { margin: 0; padding: .6rem .75rem; border-radius: var(--dm-radius-md); color: var(--dm-text);
|
|
82
|
+
background: color-mix(in srgb, var(--dm-warning) 16%, transparent); border: 1px solid color-mix(in srgb, var(--dm-warning) 35%, transparent); }
|
|
83
|
+
.atx-token { display: block; padding: .65rem; word-break: break-all; user-select: all; border-radius: var(--dm-radius-sm);
|
|
84
|
+
background: var(--dm-surface-raised, color-mix(in srgb, var(--dm-text) 6%, transparent)); color: var(--dm-text); font-family: var(--dm-font-mono, monospace); }
|
|
85
|
+
@media (max-width: 640px) { .atx-used { display: none; } }
|
|
86
|
+
</style>
|
|
@@ -1,18 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
<!-- Blocks. Bound with M.applyBindings - see admin/views/blocks.js, which also inserts the row template
|
|
2
|
+
(the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the Tool
|
|
3
|
+
banner above is the heading, and New block and Import are banner actions. -->
|
|
4
|
+
<div class="blx">
|
|
5
|
+
<p class="blx-note">Reusable HTML templates - one look for a collection's entries, a card, a call to action - placed on a
|
|
6
|
+
page with a shortcode.
|
|
7
|
+
<span data-help-title="Blocks" data-help="A block is an HTML template with a placeholder for each field (the field name in double curly braces), and optionally its own CSS. Use it to show collection entries ([collection … display="block" block="name"]), on its own ([block template="name" …]), or by its name as a short tag ([name … /]). Each block's comment at the top says what it expects; right-click a block to copy its shortcode."></span></p>
|
|
8
|
+
|
|
9
|
+
<!-- Hidden file picker driven by the Import banner action -->
|
|
10
|
+
<input class="blx-import-file" type="file" accept=".dmblock.json,application/json" hidden>
|
|
11
|
+
|
|
12
|
+
<div class="blx-card">
|
|
13
|
+
<div class="blx-bar">
|
|
14
|
+
<div class="blx-seg" role="group" aria-label="Show">
|
|
15
|
+
<button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
|
|
16
|
+
<button type="button" data-on-click="showUsed" data-bind-class="filter.value === 'used' && 'is-on'">In use <b data-bind-text="counts.value.used"></b></button>
|
|
17
|
+
<button type="button" data-on-click="showUnused" data-bind-class="filter.value === 'unused' && 'is-on'">Unused <b data-bind-text="counts.value.unused"></b></button>
|
|
18
|
+
</div>
|
|
19
|
+
<input type="search" class="form-input blx-search" placeholder="Search blocks…" aria-label="Search blocks" data-model="query.value">
|
|
20
|
+
<span data-help-title="The list" data-help="Click a block to edit it. Right-click it (or ⋮, or Shift+F10) to copy its shortcode, see where it is used, duplicate, export or delete it. In use counts pages and views that place it; a block a plugin uses from its own code does not count, so check before deleting one marked unused."></span>
|
|
21
|
+
</div>
|
|
22
|
+
<p class="blx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
23
|
+
<p class="blx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
24
|
+
<p class="blx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
25
|
+
<div class="blx-list" data-each="rows.value key=name"></div>
|
|
11
26
|
</div>
|
|
12
27
|
</div>
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
<style>
|
|
30
|
+
.blx { display: flex; flex-direction: column; gap: .75rem; }
|
|
31
|
+
.blx [hidden], .blx-form [hidden] { display: none !important; }
|
|
32
|
+
.blx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
33
|
+
.blx-note.is-bad { color: var(--dm-danger); }
|
|
34
|
+
.blx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
35
|
+
.blx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
36
|
+
.blx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
37
|
+
.blx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
38
|
+
.blx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
39
|
+
.blx-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
40
|
+
.blx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
|
|
41
|
+
.blx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
42
|
+
.blx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
43
|
+
.blx-list { display: flex; flex-direction: column; }
|
|
44
|
+
.blx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
45
|
+
.blx-row:last-child { border-bottom: 0; }
|
|
46
|
+
.blx-row:hover, .blx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
47
|
+
.blx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0;
|
|
48
|
+
border-radius: var(--dm-radius-md); color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
|
|
49
|
+
.blx-row.is-unused .blx-icon { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
|
|
50
|
+
.blx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
51
|
+
.blx-main strong, .blx-main small, .blx-main span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
52
|
+
.blx-main span { color: var(--dm-text); font-size: var(--dm-text-sm); }
|
|
53
|
+
.blx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
54
|
+
.blx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; }
|
|
55
|
+
.blx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
|
|
56
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
57
|
+
.blx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
58
|
+
.blx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
|
|
59
|
+
.blx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
60
|
+
.blx-uses { min-width: 2.5rem; text-align: right; font-variant-numeric: tabular-nums; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
61
|
+
.blx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
62
|
+
.blx-kebab:hover { color: var(--dm-primary); }
|
|
63
|
+
/* Slideovers */
|
|
64
|
+
.blx-form { display: flex; flex-direction: column; gap: .85rem; }
|
|
65
|
+
.blx-form .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
66
|
+
.blx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
67
|
+
.blx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
68
|
+
.blx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
|
|
69
|
+
.blx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
70
|
+
.blx-spacer { flex: 1; }
|
|
71
|
+
.blx-where { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
|
|
72
|
+
.blx-where li { padding: .4rem .2rem; border-bottom: 1px solid var(--dm-border); }
|
|
73
|
+
.blx-where li:last-child { border-bottom: 0; }
|
|
74
|
+
.blx-where a { color: var(--dm-primary); text-decoration: none; }
|
|
75
|
+
.blx-where small { color: var(--dm-text-muted); margin-left: .4rem; }
|
|
76
|
+
.blx-code { font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-xs); background: var(--dm-surface-raised, color-mix(in srgb, var(--dm-text) 6%, transparent));
|
|
77
|
+
border: 1px solid var(--dm-border); border-radius: var(--dm-radius-sm); padding: .4rem .5rem; word-break: break-all; }
|
|
78
|
+
@media (max-width: 640px) { .blx-uses, .blx-chips { display: none; } }
|
|
79
|
+
</style>
|
|
@@ -1,59 +1,115 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</
|
|
9
|
-
|
|
10
|
-
<div class="tabs" id="collections-tabs">
|
|
11
|
-
<div class="tab-list">
|
|
12
|
-
<button class="tab-item active">Collections</button>
|
|
13
|
-
<button class="tab-item">Options</button>
|
|
14
|
-
</div>
|
|
15
|
-
<div class="tab-content">
|
|
1
|
+
<!-- Collections. Bound with M.applyBindings - see admin/js/views/collections.js, which also inserts the row
|
|
2
|
+
templates (the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the
|
|
3
|
+
Tool banner above is the heading, and New collection is a banner action. -->
|
|
4
|
+
<div class="clx">
|
|
5
|
+
<p class="clx-note clx-scope" data-bind-hidden="!project.value">
|
|
6
|
+
<span data-bind-text="scopeText.value"></span>
|
|
7
|
+
<a href="#/collections">Show all collections</a>
|
|
8
|
+
</p>
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<div id="collections-table"></div>
|
|
10
|
+
<div class="tabs clx-tabs">
|
|
11
|
+
<div class="tab-list">
|
|
12
|
+
<button type="button" class="tab-item active">Collections <span class="clx-count" data-bind-text="kinds.value.all"></span></button>
|
|
13
|
+
<button type="button" class="tab-item" data-bind-hidden="!canConnect.value">Database connections <span class="clx-dirty" data-bind-hidden="!connDirty.value" title="Changes not saved yet">●</span></button>
|
|
22
14
|
</div>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
<!-- Collections -->
|
|
17
|
+
<div class="tab-panel active">
|
|
18
|
+
<div class="clx-card">
|
|
19
|
+
<div class="clx-bar">
|
|
20
|
+
<div class="clx-seg" role="group" aria-label="Show">
|
|
21
|
+
<button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="kinds.value.all"></b></button>
|
|
22
|
+
<button type="button" data-on-click="showOwn" data-bind-class="filter.value === 'own' && 'is-on'">Yours <b data-bind-text="kinds.value.own"></b></button>
|
|
23
|
+
<button type="button" data-on-click="showBuiltin" data-bind-class="filter.value === 'builtin' && 'is-on'">Built in <b data-bind-text="kinds.value.builtin"></b></button>
|
|
24
|
+
<button type="button" data-on-click="showPlugin" data-bind-class="filter.value === 'plugin' && 'is-on'">Plugins <b data-bind-text="kinds.value.plugin"></b></button>
|
|
25
|
+
</div>
|
|
26
|
+
<input type="search" class="form-input clx-search" placeholder="Search collections…" aria-label="Search collections" data-model="query.value">
|
|
27
|
+
<span data-help-title="Collections" data-help="A collection is a set of entries with the fields you define - products, events, testimonials. Click one to see its entries. Right-click it (or ⋮, or Shift+F10) to edit its fields and settings, open its form, copy its slug, or delete it. Built-in collections are the CMS's own and cannot be deleted; a plugin's can, but the plugin may stop working."></span>
|
|
28
|
+
<span class="clx-spacer"></span>
|
|
29
|
+
<span class="clx-note" data-bind-text="totalText.value"></span>
|
|
30
|
+
</div>
|
|
31
|
+
<p class="clx-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
32
|
+
<p class="clx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
33
|
+
<p class="clx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
|
|
34
|
+
<div class="clx-list" data-each="rows.value key=slug"></div>
|
|
35
|
+
</div>
|
|
42
36
|
</div>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
|
|
38
|
+
<!-- Database connections (admins only) -->
|
|
39
|
+
<div class="tab-panel">
|
|
40
|
+
<div class="clx-card">
|
|
41
|
+
<div class="clx-bar">
|
|
42
|
+
<strong>MongoDB connections</strong>
|
|
43
|
+
<span data-help-title="Database connections" data-help="Collections are stored as files unless their storage is set to MongoDB, which needs a connection here - usually one called default. Each needs a URI (mongodb://…) and a database name. Kept in config/connections.json; with none, every collection stays on files. Ctrl+S saves."></span>
|
|
44
|
+
<span class="clx-spacer"></span>
|
|
45
|
+
<label class="clx-toggle"><input type="checkbox" data-model="raw.value"> Raw JSON</label>
|
|
46
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="addConnection" data-bind-hidden="raw.value">
|
|
47
|
+
<span data-icon="plus" data-icon-size="14"></span> Add connection</button>
|
|
48
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="saveConnections" data-bind-disabled="savingConn.value || !connDirty.value">
|
|
49
|
+
<span data-icon="save" data-icon-size="14"></span> Save</button>
|
|
50
|
+
</div>
|
|
51
|
+
<p class="clx-note" data-bind-hidden="!connLoading.value">Loading…</p>
|
|
52
|
+
<div data-bind-hidden="raw.value || connLoading.value">
|
|
53
|
+
<p class="clx-note" data-bind-hidden="connRows.value.length">No connections - every collection is stored as files. Add one to use MongoDB.</p>
|
|
54
|
+
<div class="clx-conns" data-each="connRows.value key=id"></div>
|
|
55
|
+
</div>
|
|
56
|
+
<div data-bind-hidden="!raw.value">
|
|
57
|
+
<textarea class="form-input clx-raw" rows="12" spellcheck="false" aria-label="Connections as JSON" data-model="rawText.value"></textarea>
|
|
58
|
+
</div>
|
|
59
|
+
<p class="clx-note is-bad" data-bind-hidden="!connError.value" data-bind-text="connError.value"></p>
|
|
60
|
+
</div>
|
|
54
61
|
</div>
|
|
55
|
-
</div>
|
|
56
62
|
</div>
|
|
57
|
-
|
|
58
|
-
</div>
|
|
59
63
|
</div>
|
|
64
|
+
|
|
65
|
+
<style>
|
|
66
|
+
.clx { display: flex; flex-direction: column; gap: .75rem; }
|
|
67
|
+
.clx [hidden], .clx-form [hidden] { display: none !important; }
|
|
68
|
+
.clx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
69
|
+
.clx-note.is-bad { color: var(--dm-danger); }
|
|
70
|
+
.clx-scope { padding: .5rem .75rem; border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 10%, transparent); color: var(--dm-text); }
|
|
71
|
+
.clx-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
|
|
72
|
+
.clx-dirty { color: var(--dm-warning); margin-left: .2rem; }
|
|
73
|
+
.clx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; margin-top: .75rem; }
|
|
74
|
+
.clx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
75
|
+
.clx-bar .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
76
|
+
.clx-spacer { flex: 1; }
|
|
77
|
+
.clx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
78
|
+
.clx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
79
|
+
.clx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
80
|
+
.clx-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
81
|
+
.clx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
|
|
82
|
+
.clx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
83
|
+
.clx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
84
|
+
.clx-toggle { display: inline-flex; align-items: center; gap: .35rem; font-size: var(--dm-text-sm); cursor: pointer; }
|
|
85
|
+
.clx-list { display: flex; flex-direction: column; }
|
|
86
|
+
.clx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
|
|
87
|
+
.clx-row:last-child { border-bottom: 0; }
|
|
88
|
+
.clx-row:hover, .clx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
89
|
+
.clx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0;
|
|
90
|
+
border-radius: var(--dm-radius-md); color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
|
|
91
|
+
.clx-row.is-builtin .clx-icon { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
|
|
92
|
+
.clx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
93
|
+
.clx-main strong, .clx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
94
|
+
.clx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
95
|
+
.clx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
|
96
|
+
.clx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
|
|
97
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
98
|
+
.clx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
99
|
+
.clx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
100
|
+
.clx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
|
|
101
|
+
.clx-num { min-width: 4.5rem; text-align: right; font-variant-numeric: tabular-nums; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
102
|
+
.clx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
103
|
+
.clx-kebab:hover { color: var(--dm-primary); }
|
|
104
|
+
.clx-conns { display: flex; flex-direction: column; gap: .5rem; }
|
|
105
|
+
.clx-conn { display: grid; grid-template-columns: 1fr 2fr 1fr auto; gap: .5rem; align-items: end; padding: .6rem; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); }
|
|
106
|
+
.clx-conn label { display: flex; flex-direction: column; gap: .2rem; font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
|
|
107
|
+
.clx-raw { width: 100%; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); resize: vertical; }
|
|
108
|
+
.clx-form { display: flex; flex-direction: column; gap: .85rem; }
|
|
109
|
+
.clx-form .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
110
|
+
.clx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
111
|
+
.clx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
112
|
+
.clx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
|
|
113
|
+
.clx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
114
|
+
@media (max-width: 720px) { .clx-conn { grid-template-columns: 1fr; } .clx-num { display: none; } }
|
|
115
|
+
</style>
|