domma-cms 0.76.0 → 0.77.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 +1 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/action-editor-arrange.js +1 -0
- package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
- package/admin/js/lib/block-editor-arrange.js +2 -0
- package/admin/js/lib/collection-editor-arrange.js +3 -0
- package/admin/js/lib/collection-entries-arrange.js +1 -0
- package/admin/js/lib/component-editor-arrange.js +27 -0
- package/admin/js/lib/context-menu-editor-arrange.js +1 -0
- package/admin/js/lib/dashboard-arrange.js +1 -0
- package/admin/js/lib/form-editor-arrange.js +1 -0
- package/admin/js/lib/form-submissions-arrange.js +2 -0
- package/admin/js/lib/menu-editor-arrange.js +2 -0
- package/admin/js/lib/page-editor-arrange.js +1 -0
- package/admin/js/lib/plugin-code-arrange.js +1 -0
- package/admin/js/lib/project-page-arrange.js +1 -0
- package/admin/js/lib/role-editor-arrange.js +3 -0
- package/admin/js/lib/user-editor-arrange.js +1 -0
- package/admin/js/lib/view-editor-arrange.js +14 -0
- package/admin/js/templates/action-editor.html +223 -147
- package/admin/js/templates/api-endpoint-editor.html +172 -117
- package/admin/js/templates/block-editor.html +194 -203
- package/admin/js/templates/collection-editor.html +191 -113
- package/admin/js/templates/collection-entries.html +99 -36
- package/admin/js/templates/component-editor.html +128 -126
- package/admin/js/templates/context-menu-editor.html +208 -148
- package/admin/js/templates/dashboard.html +184 -22
- package/admin/js/templates/form-editor.html +41 -26
- package/admin/js/templates/form-submissions.html +78 -26
- package/admin/js/templates/menu-editor.html +57 -17
- package/admin/js/templates/menu-locations.html +54 -11
- package/admin/js/templates/page-editor.html +47 -26
- package/admin/js/templates/plugin-code.html +91 -73
- package/admin/js/templates/project-detail.html +83 -145
- package/admin/js/templates/project-settings.html +88 -53
- package/admin/js/templates/role-editor.html +158 -83
- package/admin/js/templates/user-editor.html +150 -14
- package/admin/js/templates/view-editor.html +203 -180
- package/admin/js/templates/view-preview.html +45 -45
- package/admin/js/views/action-editor.js +7 -1
- package/admin/js/views/api-endpoint-editor.js +2 -3
- package/admin/js/views/block-editor.js +14 -8
- package/admin/js/views/collection-editor.js +64 -5
- package/admin/js/views/collection-entries.js +50 -1
- package/admin/js/views/component-editor.js +2 -28
- package/admin/js/views/context-menu-editor.js +54 -55
- package/admin/js/views/dashboard.js +7 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +11 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +21 -43
- package/admin/js/views/menu-locations.js +16 -1
- package/admin/js/views/page-editor.js +26 -26
- package/admin/js/views/plugin-code.js +23 -4
- package/admin/js/views/project-detail.js +9 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/role-editor.js +11 -3
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/package.json +1 -1
- package/server/routes/api/dashboard.js +32 -4
- package/server/routes/api/forms.js +104 -13
- package/server/routes/api/users.js +12 -2
- package/server/routes/api/views.js +55 -1
- package/server/services/components.js +9 -0
- package/server/services/users.js +2 -2
- package/server/services/views.js +14 -0
- package/admin/js/templates/dashboard/activity-feed.html +0 -3
- package/admin/js/templates/dashboard/cache.html +0 -32
- package/admin/js/templates/dashboard/health-detail.html +0 -2
- package/admin/js/templates/dashboard/journeys.html +0 -17
- package/admin/js/templates/dashboard/kpi-strip.html +0 -34
- package/admin/js/templates/dashboard/spike-feed.html +0 -3
- package/admin/js/templates/dashboard/top-pages.html +0 -3
- package/admin/js/templates/dashboard/traffic-chart.html +0 -3
- package/admin/js/views/block-editor-enhance.js +0 -1
- package/admin/js/views/dashboard/lib/escape.js +0 -1
- package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
- package/admin/js/views/dashboard/widgets/cache.js +0 -1
- package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
- package/admin/js/views/dashboard/widgets/journeys.js +0 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
- package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
- package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
- package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
|
@@ -1,98 +1,173 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
<!-- Role editor. Bound with M.applyBindings - see admin/views/role-editor.js, which also inserts the row
|
|
2
|
+
templates (the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the
|
|
3
|
+
Tool banner is the heading ("Roles › <role>"), with Save and Back to roles in it. -->
|
|
4
|
+
<div class="rex">
|
|
5
|
+
<div class="rex-bar" data-bind-hidden="!dirty.value">
|
|
6
|
+
<span class="rex-chip is-warn">Unsaved</span>
|
|
7
|
+
<span class="rex-note">Changes to this role are not saved yet.</span>
|
|
8
|
+
<span class="rex-spacer"></span>
|
|
9
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
|
|
10
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannot.value">
|
|
11
|
+
<span data-icon="save" data-icon-size="13"></span> Save</button>
|
|
6
12
|
</div>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
<div class="tab-list">
|
|
11
|
-
<button class="tab-item active">General</button>
|
|
12
|
-
<button class="tab-item">Permissions</button>
|
|
13
|
+
<div class="rex-alert is-bad" data-bind-hidden="!risks.value.length">
|
|
14
|
+
<strong>Careful - this is your own role.</strong>
|
|
15
|
+
<ul class="rex-risks" data-each="riskRows.value key=id"></ul>
|
|
13
16
|
</div>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
<p class="rex-alert" data-bind-hidden="!isRoot">
|
|
18
|
+
The root role holds every permission - core and plugins - whatever is ticked here, and keeps its name and level.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<div class="tabs rex-tabs">
|
|
22
|
+
<div class="tab-list">
|
|
23
|
+
<button type="button" class="tab-item active">General <b class="rex-dot" data-bind-hidden="!generalDirty.value" title="Unsaved changes">•</b></button>
|
|
24
|
+
<button type="button" class="tab-item">Permissions <span class="rex-count" data-bind-text="grantedCount.value"></span>
|
|
25
|
+
<b class="rex-dot" data-bind-hidden="!permsDirty.value" title="Unsaved changes">•</b></button>
|
|
26
|
+
<button type="button" class="tab-item">What it can do</button>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- General -->
|
|
30
|
+
<div class="tab-panel active">
|
|
31
|
+
<div class="rex-card">
|
|
32
|
+
<div class="rex-card-title">Role
|
|
33
|
+
<span class="rex-badge-preview badge" data-bind-class="badgeClass.value" data-bind-text="previewLabel.value"></span>
|
|
34
|
+
<span class="rex-chip" data-bind-hidden="!holdersText.value" data-bind-text="holdersText.value"></span>
|
|
35
|
+
<span class="rex-chip" data-bind-hidden="!pluginText" data-bind-text="pluginText"></span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="rex-grid">
|
|
38
|
+
<div>
|
|
39
|
+
<label class="form-label" for="rex-label">Label <span class="text-danger">*</span>
|
|
40
|
+
<span data-help="The name people see - on the user list, the role badge and this screen."></span></label>
|
|
41
|
+
<input id="rex-label" type="text" class="form-input" maxlength="60" data-model="label.value">
|
|
42
|
+
<small class="rex-err" data-bind-hidden="!errors.value.label" data-bind-text="errors.value.label"></small>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<label class="form-label" for="rex-name">Name
|
|
46
|
+
<span data-help-title="Name" data-help="The role's id: users hold it by this name, and routes and plugins refer to it. Renaming a role leaves the users who hold it on a name that no longer exists until you move them - the screen says how many that is."></span></label>
|
|
47
|
+
<input id="rex-name" type="text" class="form-input font-mono" maxlength="60" data-model="name.value" data-bind-disabled="nameLockedText">
|
|
48
|
+
<small class="rex-hint" data-bind-hidden="!nameLockedText" data-bind-text="nameLockedText"></small>
|
|
49
|
+
<small class="rex-hint is-warn" data-bind-hidden="!renameNote.value" data-bind-text="renameNote.value"></small>
|
|
50
|
+
<small class="rex-err" data-bind-hidden="!errors.value.name" data-bind-text="errors.value.name"></small>
|
|
34
51
|
</div>
|
|
35
|
-
<div
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<p class="form-hint">Lower = more privileged. 0 is reserved for the root admin.</p>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
<div class="col-6">
|
|
44
|
-
<div class="mb-3">
|
|
45
|
-
<label class="form-label">Badge Colour</label>
|
|
46
|
-
<select id="role-badge" class="form-input"></select>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<label class="form-label" for="rex-level">Level
|
|
54
|
+
<span data-help-title="Level" data-help="Seniority: lower is more senior. A user can manage only users whose role is strictly less senior than theirs, and a page or menu item visible to a level is visible to every more senior one. 0 is the root role alone."></span></label>
|
|
55
|
+
<input id="rex-level" type="number" class="form-input" min="1" data-model="level.value" data-bind-disabled="isRoot">
|
|
56
|
+
<small class="rex-err" data-bind-hidden="!errors.value.level" data-bind-text="errors.value.level"></small>
|
|
49
57
|
</div>
|
|
50
|
-
<div
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<label class="form-label" for="rex-badge">Badge colour</label>
|
|
60
|
+
<select id="rex-badge" class="form-input" data-model="badgeClass.value"></select>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<label class="form-label" for="rex-project">Project
|
|
64
|
+
<span data-help="Files the role under a project, for the sidebar and the project's own Roles list. It does not limit what the role can do."></span></label>
|
|
65
|
+
<select id="rex-project" class="form-input" data-model="project.value"><option value="">- none -</option></select>
|
|
60
66
|
</div>
|
|
61
67
|
</div>
|
|
62
68
|
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<label class="form-label">Allowed screens</label>
|
|
74
|
-
<textarea id="role-admin-allow" class="form-input" rows="4"
|
|
75
|
-
placeholder="/portal"></textarea>
|
|
76
|
-
<p class="form-hint">One admin route per line. A route also allows everything under
|
|
77
|
-
it (<code>/portal</code> allows <code>/portal/orders</code>).</p>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
<div class="col-6">
|
|
81
|
-
<div class="mb-3">
|
|
82
|
-
<label class="form-label">Home screen</label>
|
|
83
|
-
<input id="role-admin-home" type="text" class="form-input" placeholder="/portal">
|
|
84
|
-
<p class="form-hint">Where the user lands, and where they are sent from anywhere
|
|
85
|
-
else. Defaults to the first allowed screen.</p>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
69
|
+
|
|
70
|
+
<div class="rex-card" data-bind-hidden="isRoot">
|
|
71
|
+
<div class="rex-card-title"><span data-icon="lock" data-icon-size="15"></span> Admin area
|
|
72
|
+
<span data-help-title="Admin area" data-help="Leave both blank to let this role use every admin screen its permissions allow. Fill in the allowed screens to confine it - customers who should only ever see their own portal, say. A user is confined only if EVERY role they hold is; My Profile is always open."></span>
|
|
73
|
+
<span class="rex-chip is-warn" data-bind-hidden="!scopeText.value" data-bind-text="scopeText.value"></span></div>
|
|
74
|
+
<div class="rex-grid">
|
|
75
|
+
<div>
|
|
76
|
+
<label class="form-label" for="rex-allow">Allowed screens
|
|
77
|
+
<span data-help="One admin route per line. A route also allows everything under it: /portal allows /portal/orders."></span></label>
|
|
78
|
+
<textarea id="rex-allow" class="form-input font-mono" rows="4" placeholder="/portal" data-model="adminAllow.value"></textarea>
|
|
88
79
|
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<label class="form-label" for="rex-home">Home screen
|
|
82
|
+
<span data-help="Where holders land after signing in, and where they are sent from anywhere they may not go. Defaults to the first allowed screen."></span></label>
|
|
83
|
+
<input id="rex-home" type="text" class="form-input font-mono" placeholder="/portal" data-model="adminHome.value">
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<small class="rex-err" data-bind-hidden="!errors.value.admin" data-bind-text="errors.value.admin"></small>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- Permissions -->
|
|
91
|
+
<div class="tab-panel">
|
|
92
|
+
<div class="rex-card" data-bind-class="isRoot && 'is-locked'">
|
|
93
|
+
<div class="rex-bar-tools">
|
|
94
|
+
<input type="search" class="form-input rex-search" placeholder="Search permissions…" aria-label="Search permissions" data-model="query.value">
|
|
95
|
+
<div class="rex-seg" role="group" aria-label="Show">
|
|
96
|
+
<button type="button" data-on-click="showAll" data-bind-class="show.value === 'all' && 'is-on'">All</button>
|
|
97
|
+
<button type="button" data-on-click="showOn" data-bind-class="show.value === 'on' && 'is-on'">Granted</button>
|
|
98
|
+
<button type="button" data-on-click="showOff" data-bind-class="show.value === 'off' && 'is-on'">Not granted</button>
|
|
99
|
+
</div>
|
|
100
|
+
<span data-help-title="Permissions" data-help="Click an action to grant or take it away; Full turns a whole area on or off. Right-click an area (or ⋮, or Shift+F10) for view only or none; right-click a group heading to grant or clear the whole group. Plugins add their own areas under Plugins."></span>
|
|
101
|
+
<span class="rex-spacer"></span>
|
|
102
|
+
<span class="rex-note" data-bind-text="grantedText.value"></span>
|
|
89
103
|
</div>
|
|
104
|
+
<p class="rex-note" data-bind-hidden="rows.value.length">Nothing matches.</p>
|
|
105
|
+
<div class="rex-matrix" data-each="rows.value key=id"></div>
|
|
90
106
|
</div>
|
|
91
107
|
</div>
|
|
92
108
|
|
|
93
|
-
<!--
|
|
94
|
-
<div class="tab-panel"
|
|
95
|
-
<div
|
|
109
|
+
<!-- What it can do -->
|
|
110
|
+
<div class="tab-panel">
|
|
111
|
+
<div class="rex-card">
|
|
112
|
+
<div class="rex-card-title">What <span data-bind-text="previewLabel.value"></span> can do
|
|
113
|
+
<span data-help="Read back from the ticks as they are now - saved or not. Anyone holding this role and another gets both roles' permissions together."></span></div>
|
|
114
|
+
<div class="rex-summary" data-each="summary.value key=id"></div>
|
|
115
|
+
</div>
|
|
96
116
|
</div>
|
|
97
117
|
</div>
|
|
98
118
|
</div>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.rex { display: flex; flex-direction: column; gap: .75rem; }
|
|
122
|
+
.rex [hidden] { display: none !important; }
|
|
123
|
+
.rex-bar { display: flex; align-items: center; gap: .5rem; padding: .5rem .75rem; border: 1px solid var(--dm-border);
|
|
124
|
+
border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-warning) 10%, transparent); position: sticky; top: 0; z-index: 5; }
|
|
125
|
+
.rex-bar .btn, .rex-card-title .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
126
|
+
.rex-alert { margin: 0; padding: .6rem .8rem; border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 12%, transparent); color: var(--dm-text); }
|
|
127
|
+
.rex-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dm-danger) 35%, transparent); }
|
|
128
|
+
.rex-risks { margin: .35rem 0 0; padding-left: 1.2rem; }
|
|
129
|
+
.rex-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .7rem; margin-top: .75rem; }
|
|
130
|
+
.rex-card-title { display: flex; align-items: center; gap: .45rem; font-weight: 600; flex-wrap: wrap; }
|
|
131
|
+
.rex-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .85rem; }
|
|
132
|
+
.rex-grid .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
133
|
+
.rex-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
134
|
+
.rex-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
135
|
+
.rex-hint.is-warn { color: var(--dm-warning); }
|
|
136
|
+
.rex-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
137
|
+
.rex-spacer { flex: 1; }
|
|
138
|
+
.rex-dot { color: var(--dm-warning); margin-left: .2rem; }
|
|
139
|
+
.rex-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
|
|
140
|
+
.rex-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;
|
|
141
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
142
|
+
.rex-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
143
|
+
.rex-badge-preview { font-weight: 600; }
|
|
144
|
+
.rex-bar-tools { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
145
|
+
.rex-search { flex: 1; min-width: 10rem; max-width: 20rem; }
|
|
146
|
+
.rex-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
147
|
+
.rex-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
|
|
148
|
+
.rex-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
149
|
+
.rex-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
150
|
+
.rex-matrix { display: flex; flex-direction: column; }
|
|
151
|
+
.rex-row { display: flex; align-items: center; gap: .6rem; padding: .45rem .35rem; border-bottom: 1px solid var(--dm-border); }
|
|
152
|
+
.rex-row:focus-visible, .rex-row:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
153
|
+
.rex-row.is-group { background: color-mix(in srgb, var(--dm-text) 5%, transparent); border-top: 1px solid var(--dm-border); margin-top: .5rem; }
|
|
154
|
+
.rex-row.is-group .rex-main strong { text-transform: uppercase; letter-spacing: .04em; font-size: var(--dm-text-xs); }
|
|
155
|
+
.rex-icon { display: inline-flex; width: 1.6rem; justify-content: center; color: var(--dm-text-muted); flex-shrink: 0; }
|
|
156
|
+
.rex-main { min-width: 11rem; flex: 0 1 16rem; display: flex; flex-direction: column; }
|
|
157
|
+
.rex-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
158
|
+
.rex-acts { display: flex; flex-wrap: wrap; gap: .3rem; flex: 1; }
|
|
159
|
+
.rex-act, .rex-full { border: 1px solid var(--dm-border); background: none; color: var(--dm-text-muted); border-radius: 999px; padding: .15rem .6rem;
|
|
160
|
+
font: inherit; font-size: var(--dm-text-xs); cursor: pointer; }
|
|
161
|
+
.rex-act[aria-pressed="true"] { background: color-mix(in srgb, var(--dm-success) 16%, transparent); border-color: color-mix(in srgb, var(--dm-success) 45%, transparent); color: var(--dm-success); font-weight: 600; }
|
|
162
|
+
.rex-full[data-state="all"] { background: var(--dm-primary); border-color: var(--dm-primary); color: var(--dm-primary-text); }
|
|
163
|
+
.rex-full[data-state="some"] { border-style: dashed; color: var(--dm-text); }
|
|
164
|
+
.rex-act:disabled, .rex-full:disabled, .is-locked .rex-act { cursor: not-allowed; opacity: .6; }
|
|
165
|
+
.rex-state { font-size: var(--dm-text-xs); color: var(--dm-text-muted); min-width: 6rem; text-align: right; }
|
|
166
|
+
.rex-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; }
|
|
167
|
+
.rex-kebab:hover { color: var(--dm-primary); }
|
|
168
|
+
.rex-summary { display: flex; flex-direction: column; }
|
|
169
|
+
.rex-sum { display: grid; grid-template-columns: 9rem 1fr; gap: 1rem; padding: .45rem 0; border-bottom: 1px solid var(--dm-border); }
|
|
170
|
+
.rex-sum:last-child { border-bottom: 0; }
|
|
171
|
+
.rex-sum strong { font-weight: 600; }
|
|
172
|
+
@media (max-width: 720px) { .rex-grid { grid-template-columns: 1fr; } .rex-main { flex-basis: 100%; } .rex-state { display: none; } }
|
|
173
|
+
</style>
|
|
@@ -1,19 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<!-- User editor (#/users/new, #/users/edit/:id). Bound with M.applyBindings - see
|
|
2
|
+
admin/js/views/user-editor.js, which also draws the profile fields from the user-profiles schema and inserts
|
|
3
|
+
the role / project chip templates (the router renders a templateUrl through _.render, eating any {{…}}).
|
|
4
|
+
No page heading: the Tool banner ("Users › <name>") is the heading; Back and Save are banner actions. -->
|
|
5
|
+
<div class="uex">
|
|
6
|
+
<p class="uex-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
|
|
7
|
+
<p class="uex-note" data-bind-hidden="!loading.value">Loading…</p>
|
|
8
|
+
|
|
9
|
+
<p class="uex-alert" data-bind-hidden="loading.value || !lockedReason.value">
|
|
10
|
+
<span data-icon="lock" data-icon-size="15"></span>
|
|
11
|
+
<span data-bind-text="lockedReason.value"></span>
|
|
12
|
+
</p>
|
|
7
13
|
|
|
8
|
-
<div class="
|
|
9
|
-
|
|
10
|
-
<
|
|
14
|
+
<div class="uex-dirty" data-bind-hidden="!dirty.value">
|
|
15
|
+
<span data-icon="edit" data-icon-size="14"></span>
|
|
16
|
+
<span data-bind-text="dirtyText">You have unsaved changes.</span>
|
|
17
|
+
<span class="uex-spacer"></span>
|
|
18
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
|
|
19
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannotSave.value">
|
|
20
|
+
<span data-icon="check" data-icon-size="13"></span> <span data-bind-text="saveText">Save</span></button>
|
|
11
21
|
</div>
|
|
12
|
-
</div>
|
|
13
22
|
|
|
14
|
-
<div class="card
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
<div class="uex-card uex-who" data-bind-hidden="loading.value || loadError.value">
|
|
24
|
+
<span class="uex-avatar" aria-hidden="true" data-bind-text="avatar.value"></span>
|
|
25
|
+
<span class="uex-who-main">
|
|
26
|
+
<strong data-bind-text="shownName.value"></strong>
|
|
27
|
+
<small data-bind-text="shownEmail.value"></small>
|
|
28
|
+
<small class="uex-facts" data-bind-text="factsText.value"></small>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="uex-chips">
|
|
31
|
+
<span class="uex-chip is-primary" data-bind-hidden="!isSelf.value">You</span>
|
|
32
|
+
<span class="uex-chip is-ok" data-bind-text="roleText.value"></span>
|
|
33
|
+
<span class="uex-chip is-warn" data-bind-hidden="isActive.value">Inactive</span>
|
|
34
|
+
</span>
|
|
18
35
|
</div>
|
|
36
|
+
|
|
37
|
+
<fieldset class="uex-fields" data-bind-disabled="locked.value || saving.value" data-bind-hidden="loading.value || loadError.value">
|
|
38
|
+
<div class="uex-card">
|
|
39
|
+
<div class="uex-card-title">Account
|
|
40
|
+
<span data-help="The name shown in the admin and on anything they author. The email is what they sign in with, and where password resets go - no two accounts may share one."></span></div>
|
|
41
|
+
<div class="uex-grid">
|
|
42
|
+
<div>
|
|
43
|
+
<label class="form-label" for="uex-name">Full name <span class="text-danger">*</span></label>
|
|
44
|
+
<input id="uex-name" type="text" class="form-input" maxlength="100" autocomplete="off" placeholder="Jane Smith" data-model="name.value">
|
|
45
|
+
<small class="uex-err" data-bind-hidden="!err.value.name" data-bind-text="err.value.name"></small>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<label class="form-label" for="uex-email">Email <span class="text-danger">*</span></label>
|
|
49
|
+
<input id="uex-email" type="email" class="form-input" maxlength="200" autocomplete="off" placeholder="jane@example.com" data-model="email.value">
|
|
50
|
+
<small class="uex-err" data-bind-hidden="!emailErr.value" data-bind-text="emailErr.value"></small>
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<label class="form-label" for="uex-pw"><span data-bind-text="pwLabel.value">Password</span>
|
|
54
|
+
<span data-help-title="Password" data-help="At least 8 characters. When editing, leave it empty to keep the current password. It is never kept as a draft - if you leave the screen, it is typed again."></span></label>
|
|
55
|
+
<input id="uex-pw" type="password" data-bind-type="showPw.value ? 'text' : 'password'" class="form-input" autocomplete="new-password"
|
|
56
|
+
data-bind-placeholder="pwPlaceholder.value" data-model="password.value">
|
|
57
|
+
<small class="uex-err" data-bind-hidden="!err.value.password" data-bind-text="err.value.password"></small>
|
|
58
|
+
<label class="uex-show"><input type="checkbox" data-model="showPw.value"> Show what I type</label>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="uex-setting">
|
|
61
|
+
<label class="form-label" for="uex-active">Active account
|
|
62
|
+
<span data-help="An inactive account cannot sign in. Nothing it made is removed - switch it back on at any time. You cannot switch off your own account."></span></label>
|
|
63
|
+
<input id="uex-active" type="checkbox" class="form-switch" data-model="isActive.value" data-bind-disabled="isSelf.value"
|
|
64
|
+
data-bind-title="isSelf.value ? 'You cannot switch off your own account' : ''">
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="uex-card">
|
|
70
|
+
<div class="uex-card-title">Roles
|
|
71
|
+
<span data-help-title="Roles" data-help="The primary role is the badge shown beside their name, and what seniority is compared on. Additional roles add their permissions on top - someone can be, say, a recruiter AND a content editor; the most senior role they hold decides who may manage them. You can only give roles below your own; a role they already hold can always be kept."></span></div>
|
|
72
|
+
<div class="uex-grid">
|
|
73
|
+
<div>
|
|
74
|
+
<label class="form-label" for="uex-role">Primary role <span class="text-danger">*</span></label>
|
|
75
|
+
<select id="uex-role" class="form-input uex-role-select" data-model="role.value"></select>
|
|
76
|
+
<small class="uex-err" data-bind-hidden="!err.value.role" data-bind-text="err.value.role"></small>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="uex-wide">
|
|
79
|
+
<span class="form-label">Additional roles</span>
|
|
80
|
+
<div class="uex-pick uex-roles" data-each="extraChips.value key=name"></div>
|
|
81
|
+
<small class="uex-hint" data-bind-hidden="extraChips.value.length">No other roles to add.</small>
|
|
82
|
+
<small class="uex-err" data-bind-hidden="!err.value.additionalRoles" data-bind-text="err.value.additionalRoles"></small>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div class="uex-card" data-bind-hidden="!hasProjects.value">
|
|
88
|
+
<div class="uex-card-title">Projects
|
|
89
|
+
<span data-help-title="Projects" data-help="Owned by: where this user is filed in the sidebar's Projects section - administrative only. Access: which projects' pages, collections and forms they can see and edit - leave it all unticked for no restriction. Core is always visible."></span></div>
|
|
90
|
+
<div class="uex-grid">
|
|
91
|
+
<div>
|
|
92
|
+
<label class="form-label" for="uex-owner">Owned by project</label>
|
|
93
|
+
<select id="uex-owner" name="ownedByProject" class="form-input uex-owner-select" data-model="ownedByProject.value"></select>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="uex-wide">
|
|
96
|
+
<span class="form-label">Project access</span>
|
|
97
|
+
<div class="uex-pick uex-projects" data-each="projectChips.value key=slug"></div>
|
|
98
|
+
<small class="uex-hint" data-bind-text="accessHint.value"></small>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="uex-card" data-bind-hidden="!hasProfile.value">
|
|
104
|
+
<div class="uex-card-title">Profile
|
|
105
|
+
<span data-help="Extra details about this user. Which ones are asked for is set in the User Profiles collection."></span></div>
|
|
106
|
+
<div class="uex-grid uex-profile"></div>
|
|
107
|
+
</div>
|
|
108
|
+
</fieldset>
|
|
19
109
|
</div>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.uex { display: flex; flex-direction: column; gap: .75rem; max-width: 60rem; }
|
|
113
|
+
.uex [hidden] { display: none !important; }
|
|
114
|
+
.uex-fields { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: .75rem; }
|
|
115
|
+
.uex-fields:disabled { opacity: .75; }
|
|
116
|
+
.uex-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
117
|
+
.uex-note.is-bad { color: var(--dm-danger); }
|
|
118
|
+
.uex-alert { margin: 0; display: flex; align-items: center; gap: .5rem; padding: .55rem .8rem; border-radius: var(--dm-radius-md);
|
|
119
|
+
background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
|
|
120
|
+
.uex-dirty { display: flex; align-items: center; gap: .5rem; padding: .5rem .8rem; border-radius: var(--dm-radius-md);
|
|
121
|
+
background: color-mix(in srgb, var(--dm-warning) 14%, transparent); color: var(--dm-text); position: sticky; top: 0; z-index: 2; }
|
|
122
|
+
.uex-dirty .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
123
|
+
.uex-spacer { flex: 1; }
|
|
124
|
+
.uex-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .8rem .9rem; display: flex; flex-direction: column; gap: .7rem; }
|
|
125
|
+
.uex-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
|
|
126
|
+
.uex-who { flex-direction: row; align-items: center; gap: .9rem; flex-wrap: wrap; }
|
|
127
|
+
.uex-avatar { display: inline-flex; align-items: center; justify-content: center; width: 3rem; height: 3rem; border-radius: 50%; flex-shrink: 0;
|
|
128
|
+
font-weight: 700; color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 14%, transparent); }
|
|
129
|
+
.uex-who-main { flex: 1; min-width: 12rem; display: flex; flex-direction: column; }
|
|
130
|
+
.uex-who-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
131
|
+
.uex-facts { margin-top: .15rem; }
|
|
132
|
+
.uex-chips { display: inline-flex; gap: .3rem; flex-wrap: wrap; }
|
|
133
|
+
.uex-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .55rem; border-radius: 999px; white-space: nowrap;
|
|
134
|
+
background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
|
|
135
|
+
.uex-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
|
|
136
|
+
.uex-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
|
|
137
|
+
.uex-chip.is-primary { background: color-mix(in srgb, var(--dm-primary) 16%, transparent); color: var(--dm-primary); }
|
|
138
|
+
.uex-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
|
|
139
|
+
.uex-wide { grid-column: 1 / -1; }
|
|
140
|
+
.uex .form-label { display: flex; align-items: center; gap: .3rem; }
|
|
141
|
+
.uex-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
142
|
+
.uex-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
143
|
+
.uex-show { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--dm-text-xs); color: var(--dm-text-muted); cursor: pointer; margin-top: .3rem; }
|
|
144
|
+
.uex-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; align-self: end; padding-bottom: .4rem; }
|
|
145
|
+
.uex-setting .form-label { margin: 0; }
|
|
146
|
+
.uex-pick { display: flex; flex-wrap: wrap; gap: .35rem; }
|
|
147
|
+
.uex-pick-chip { display: inline-flex; align-items: center; gap: .3rem; border: 1px solid var(--dm-border); border-radius: 999px; padding: .2rem .65rem;
|
|
148
|
+
background: none; color: var(--dm-text); font: inherit; font-size: var(--dm-text-sm); cursor: pointer; }
|
|
149
|
+
.uex-pick-chip:hover:not(:disabled) { border-color: var(--dm-primary); }
|
|
150
|
+
.uex-pick-chip.is-on { background: color-mix(in srgb, var(--dm-primary) 14%, transparent); border-color: var(--dm-primary); color: var(--dm-primary); }
|
|
151
|
+
.uex-pick-chip:disabled { opacity: .5; cursor: not-allowed; }
|
|
152
|
+
.uex-pick-chip small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
153
|
+
.uex-check { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; margin-right: .8rem; }
|
|
154
|
+
@media (max-width: 640px) { .uex-grid { grid-template-columns: 1fr; } }
|
|
155
|
+
</style>
|