domma-cms 0.25.15 → 0.30.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/admin/js/app.js +2 -2
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/templates/api-reference.html +68 -1233
- package/admin/js/templates/docs/api-actions.html +146 -0
- package/admin/js/templates/docs/api-authentication.html +202 -0
- package/admin/js/templates/docs/api-collections.html +347 -0
- package/admin/js/templates/docs/api-layouts.html +123 -0
- package/admin/js/templates/docs/api-media.html +158 -0
- package/admin/js/templates/docs/api-navigation.html +99 -0
- package/admin/js/templates/docs/api-pages.html +214 -0
- package/admin/js/templates/docs/api-plugins.html +136 -0
- package/admin/js/templates/docs/api-settings.html +148 -0
- package/admin/js/templates/docs/api-users.html +189 -0
- package/admin/js/templates/docs/api-views.html +142 -0
- package/admin/js/templates/docs/components-howto.html +175 -0
- package/admin/js/templates/docs/components-reference.html +259 -0
- package/admin/js/templates/docs/components-rules.html +167 -0
- package/admin/js/templates/docs/components-walkthrough.html +167 -0
- package/admin/js/templates/docs/tutorial-crud.html +344 -0
- package/admin/js/templates/docs/tutorial-forms.html +93 -0
- package/admin/js/templates/docs/tutorial-plugin.html +234 -0
- package/admin/js/templates/docs/usage-actions.html +122 -0
- package/admin/js/templates/docs/usage-cta-shortcode.html +124 -0
- package/admin/js/templates/docs/usage-dconfig.html +148 -0
- package/admin/js/templates/docs/usage-media.html +25 -0
- package/admin/js/templates/docs/usage-navigation.html +31 -0
- package/admin/js/templates/docs/usage-pages.html +68 -0
- package/admin/js/templates/docs/usage-plugins.html +36 -0
- package/admin/js/templates/docs/usage-shortcodes.html +810 -0
- package/admin/js/templates/docs/usage-site-settings.html +52 -0
- package/admin/js/templates/docs/usage-users-roles.html +84 -0
- package/admin/js/templates/docs/usage-views.html +105 -0
- package/admin/js/templates/documentation.html +60 -1522
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/project-settings.html +7 -0
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/admin/js/templates/tutorials.html +46 -659
- package/admin/js/views/api-reference.js +1 -1
- package/admin/js/views/doc-pages.js +1 -0
- package/admin/js/views/documentation.js +1 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +3 -7
- package/admin/js/views/tutorials.js +1 -1
- package/bin/lib/config-merge.js +44 -44
- package/config/connections.json.bak +9 -0
- package/config/menus/admin-sidebar.json +76 -6
- package/package.json +1 -1
- package/public/js/site.js +1 -1
- package/scripts/migrate-docs.js +280 -0
- package/server/middleware/auth.js +253 -253
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/collections.js +10 -1
- package/server/routes/api/navigation.js +42 -42
- package/server/routes/api/projects.js +9 -1
- package/server/routes/api/settings.js +141 -141
- package/server/routes/public.js +9 -0
- package/server/server.js +6 -2
- package/server/services/email.js +167 -167
- package/server/services/presetCollections.js +1 -0
- package/server/services/projects.js +72 -3
- package/server/services/sidebar-migration.js +145 -3
- package/server/services/userProfiles.js +199 -199
- package/server/services/users.js +302 -302
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="code"></span> API · Users</h1>
|
|
3
|
+
<a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
|
|
4
|
+
</div>
|
|
5
|
+
<style>
|
|
6
|
+
.method-badge {
|
|
7
|
+
display: inline-block;
|
|
8
|
+
font-size: 11px;
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
letter-spacing: 0.5px;
|
|
11
|
+
padding: 2px 7px;
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
font-family: var(--dm-font-mono, monospace);
|
|
14
|
+
margin-right: 6px;
|
|
15
|
+
vertical-align: middle;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.method-get {
|
|
19
|
+
background: #d1fae5;
|
|
20
|
+
color: #065f46;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.method-post {
|
|
24
|
+
background: #dbeafe;
|
|
25
|
+
color: #1e40af;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.method-put {
|
|
29
|
+
background: #fef3c7;
|
|
30
|
+
color: #92400e;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.method-patch {
|
|
34
|
+
background: #ccfbf1;
|
|
35
|
+
color: #134e4a;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.method-delete {
|
|
39
|
+
background: #fee2e2;
|
|
40
|
+
color: #991b1b;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.endpoint-path {
|
|
44
|
+
font-family: var(--dm-font-mono, monospace);
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.auth-note {
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
color: var(--dm-color-text-muted, #6b7280);
|
|
51
|
+
margin: 4px 0 12px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.auth-note code {
|
|
55
|
+
font-size: 11px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.docs-body h3 {
|
|
59
|
+
margin-top: 24px;
|
|
60
|
+
margin-bottom: 8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.docs-body h3:first-child {
|
|
64
|
+
margin-top: 0;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
|
|
68
|
+
<div class="row">
|
|
69
|
+
<div class="col-12">
|
|
70
|
+
<div class="docs-body">
|
|
71
|
+
|
|
72
|
+
<p>Role hierarchy governs which users can manage other users. A manager cannot create, edit, or delete
|
|
73
|
+
an admin.
|
|
74
|
+
Self-deletion is always blocked.</p>
|
|
75
|
+
|
|
76
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/users</span></h3>
|
|
77
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission (admin or manager).</p>
|
|
78
|
+
<p>Return all users. Passwords are stripped from the response.</p>
|
|
79
|
+
<pre class="code-block"><code>// Response 200
|
|
80
|
+
[ { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true } ]</code></pre>
|
|
81
|
+
|
|
82
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/users/:id</span>
|
|
83
|
+
</h3>
|
|
84
|
+
<p class="auth-note">Requires Bearer token. Accessible to the user themselves, or a user with <code>users</code>
|
|
85
|
+
permission.</p>
|
|
86
|
+
<p>Return a single user by ID.</p>
|
|
87
|
+
<pre class="code-block"><code>// Response 200
|
|
88
|
+
{ "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true }
|
|
89
|
+
|
|
90
|
+
// Error 404
|
|
91
|
+
{ "error": "User not found" }</code></pre>
|
|
92
|
+
|
|
93
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/users</span>
|
|
94
|
+
</h3>
|
|
95
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
96
|
+
<p>Create a new user. The actor cannot assign a role higher than their own level.</p>
|
|
97
|
+
<table class="table table-sm">
|
|
98
|
+
<thead>
|
|
99
|
+
<tr>
|
|
100
|
+
<th>Field</th>
|
|
101
|
+
<th>Type</th>
|
|
102
|
+
<th>Description</th>
|
|
103
|
+
</tr>
|
|
104
|
+
</thead>
|
|
105
|
+
<tbody>
|
|
106
|
+
<tr>
|
|
107
|
+
<td><code>name</code></td>
|
|
108
|
+
<td>string</td>
|
|
109
|
+
<td>Display name</td>
|
|
110
|
+
</tr>
|
|
111
|
+
<tr>
|
|
112
|
+
<td><code>email</code></td>
|
|
113
|
+
<td>string</td>
|
|
114
|
+
<td>Unique email address</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td><code>password</code></td>
|
|
118
|
+
<td>string</td>
|
|
119
|
+
<td>Minimum 8 characters</td>
|
|
120
|
+
</tr>
|
|
121
|
+
<tr>
|
|
122
|
+
<td><code>role</code></td>
|
|
123
|
+
<td>string</td>
|
|
124
|
+
<td>Optional. Defaults to <code>editor</code>.</td>
|
|
125
|
+
</tr>
|
|
126
|
+
</tbody>
|
|
127
|
+
</table>
|
|
128
|
+
<pre class="code-block"><code>// Response 201
|
|
129
|
+
{ "id": "uuid", "name": "Bob", "email": "bob@example.com", "role": "editor", "isActive": true }
|
|
130
|
+
|
|
131
|
+
// Error 409
|
|
132
|
+
{ "error": "Email already in use" }</code></pre>
|
|
133
|
+
|
|
134
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/users/:id</span>
|
|
135
|
+
</h3>
|
|
136
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
137
|
+
<p>Update a user's details. Managers cannot edit admins. Role escalation beyond the actor's own level
|
|
138
|
+
is blocked.</p>
|
|
139
|
+
<table class="table table-sm">
|
|
140
|
+
<thead>
|
|
141
|
+
<tr>
|
|
142
|
+
<th>Field</th>
|
|
143
|
+
<th>Type</th>
|
|
144
|
+
<th>Description</th>
|
|
145
|
+
</tr>
|
|
146
|
+
</thead>
|
|
147
|
+
<tbody>
|
|
148
|
+
<tr>
|
|
149
|
+
<td><code>name</code></td>
|
|
150
|
+
<td>string</td>
|
|
151
|
+
<td>New display name</td>
|
|
152
|
+
</tr>
|
|
153
|
+
<tr>
|
|
154
|
+
<td><code>email</code></td>
|
|
155
|
+
<td>string</td>
|
|
156
|
+
<td>New email address</td>
|
|
157
|
+
</tr>
|
|
158
|
+
<tr>
|
|
159
|
+
<td><code>password</code></td>
|
|
160
|
+
<td>string</td>
|
|
161
|
+
<td>New password (min 8 chars)</td>
|
|
162
|
+
</tr>
|
|
163
|
+
<tr>
|
|
164
|
+
<td><code>role</code></td>
|
|
165
|
+
<td>string</td>
|
|
166
|
+
<td>New role</td>
|
|
167
|
+
</tr>
|
|
168
|
+
<tr>
|
|
169
|
+
<td><code>isActive</code></td>
|
|
170
|
+
<td>boolean</td>
|
|
171
|
+
<td>Enable or disable the account</td>
|
|
172
|
+
</tr>
|
|
173
|
+
</tbody>
|
|
174
|
+
</table>
|
|
175
|
+
<pre class="code-block"><code>// Response 200 — returns the updated user object</code></pre>
|
|
176
|
+
|
|
177
|
+
<h3><span class="method-badge method-delete">DELETE</span><span
|
|
178
|
+
class="endpoint-path">/api/users/:id</span></h3>
|
|
179
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
180
|
+
<p>Delete a user. Cannot delete your own account or a user with a higher role level.</p>
|
|
181
|
+
<pre class="code-block"><code>// Response 200
|
|
182
|
+
{ "success": true }
|
|
183
|
+
|
|
184
|
+
// Error 403
|
|
185
|
+
{ "error": "You cannot delete your own account" }</code></pre>
|
|
186
|
+
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="code"></span> API · Views API</h1>
|
|
3
|
+
<a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
|
|
4
|
+
</div>
|
|
5
|
+
<style>
|
|
6
|
+
.method-badge {
|
|
7
|
+
display: inline-block;
|
|
8
|
+
font-size: 11px;
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
letter-spacing: 0.5px;
|
|
11
|
+
padding: 2px 7px;
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
font-family: var(--dm-font-mono, monospace);
|
|
14
|
+
margin-right: 6px;
|
|
15
|
+
vertical-align: middle;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.method-get {
|
|
19
|
+
background: #d1fae5;
|
|
20
|
+
color: #065f46;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.method-post {
|
|
24
|
+
background: #dbeafe;
|
|
25
|
+
color: #1e40af;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.method-put {
|
|
29
|
+
background: #fef3c7;
|
|
30
|
+
color: #92400e;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.method-patch {
|
|
34
|
+
background: #ccfbf1;
|
|
35
|
+
color: #134e4a;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.method-delete {
|
|
39
|
+
background: #fee2e2;
|
|
40
|
+
color: #991b1b;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.endpoint-path {
|
|
44
|
+
font-family: var(--dm-font-mono, monospace);
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.auth-note {
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
color: var(--dm-color-text-muted, #6b7280);
|
|
51
|
+
margin: 4px 0 12px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.auth-note code {
|
|
55
|
+
font-size: 11px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.docs-body h3 {
|
|
59
|
+
margin-top: 24px;
|
|
60
|
+
margin-bottom: 8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.docs-body h3:first-child {
|
|
64
|
+
margin-top: 0;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
|
|
68
|
+
<div class="row">
|
|
69
|
+
<div class="col-12">
|
|
70
|
+
<div class="docs-body">
|
|
71
|
+
|
|
72
|
+
<p>Views require a MongoDB connection. All admin endpoints require authentication and the
|
|
73
|
+
<code>views</code> permission. View configs are stored in the <code>cms__views</code> MongoDB
|
|
74
|
+
collection.</p>
|
|
75
|
+
|
|
76
|
+
<h3 style="margin-top:16px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
77
|
+
Admin Endpoints</h3>
|
|
78
|
+
|
|
79
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views</span></h3>
|
|
80
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
81
|
+
<p>List all view configs, sorted by creation date descending.</p>
|
|
82
|
+
|
|
83
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/views</span>
|
|
84
|
+
</h3>
|
|
85
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
86
|
+
<p>Create a new view config. Returns <code>201</code> on success.</p>
|
|
87
|
+
<pre class="code-block"><code>{
|
|
88
|
+
"title": "Active Premium Users",
|
|
89
|
+
"slug": "active-premium-users",
|
|
90
|
+
"connection": "default",
|
|
91
|
+
"pipeline": {
|
|
92
|
+
"source": "users",
|
|
93
|
+
"stages": [
|
|
94
|
+
{ "type": "$match", "config": { "data.status": "active" } },
|
|
95
|
+
{ "type": "$sort", "config": { "meta.createdAt": -1 } },
|
|
96
|
+
{ "type": "$project", "config": { "data.name": 1, "data.email": 1 } }
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"display": { "mode": "table", "columns": [ { "key": "data.name", "label": "Name" } ], "pageSize": 25 },
|
|
100
|
+
"access": { "roles": ["admin", "manager"], "public": false }
|
|
101
|
+
}</code></pre>
|
|
102
|
+
|
|
103
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
104
|
+
class="endpoint-path">/api/views/:slug</span></h3>
|
|
105
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
106
|
+
<p>Return a single view config by slug.</p>
|
|
107
|
+
|
|
108
|
+
<h3><span class="method-badge method-put">PUT</span><span
|
|
109
|
+
class="endpoint-path">/api/views/:slug</span></h3>
|
|
110
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
111
|
+
<p>Update a view config. Accepts the same body shape as POST; all fields are optional.</p>
|
|
112
|
+
|
|
113
|
+
<h3><span class="method-badge method-delete">DELETE</span><span
|
|
114
|
+
class="endpoint-path">/api/views/:slug</span></h3>
|
|
115
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
116
|
+
<p>Delete a view config.</p>
|
|
117
|
+
|
|
118
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/:slug/execute</span>
|
|
119
|
+
</h3>
|
|
120
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
121
|
+
<p>Execute the view's aggregation pipeline and return paginated results. Query params:
|
|
122
|
+
<code>page</code> (default 1), <code>limit</code> (default 25).</p>
|
|
123
|
+
<pre class="code-block"><code>// Response
|
|
124
|
+
{ "results": [ ... ], "total": 142, "page": 1, "limit": 25 }</code></pre>
|
|
125
|
+
|
|
126
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/collection/:slug</span>
|
|
127
|
+
</h3>
|
|
128
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
129
|
+
<p>List all view configs whose <code>pipeline.source</code> matches the given collection slug.</p>
|
|
130
|
+
|
|
131
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
132
|
+
Public Endpoint</h3>
|
|
133
|
+
|
|
134
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/:slug/public</span>
|
|
135
|
+
</h3>
|
|
136
|
+
<p class="auth-note">Access level: per view <code>access</code> config</p>
|
|
137
|
+
<p>Execute the view publicly. If <code>access.public</code> is <code>false</code>, a valid JWT and a
|
|
138
|
+
role listed in <code>access.roles</code> is required.</p>
|
|
139
|
+
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="component"></span> Components — How-To</h1>
|
|
3
|
+
<a href="#/components/new" class="btn btn-primary btn-sm"><span data-icon="plus"></span> New Component</a>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<div class="docs-body">
|
|
9
|
+
|
|
10
|
+
<nav class="alert alert-info" style="display:flex;gap:1rem;flex-wrap:wrap;align-items:center;">
|
|
11
|
+
<strong>Components docs:</strong>
|
|
12
|
+
<a href="#/docs/components">Reference</a>
|
|
13
|
+
<a href="#/docs/components-howto">How-To</a>
|
|
14
|
+
<a href="#/docs/components-walkthrough">Walkthrough</a>
|
|
15
|
+
<a href="#/docs/components-rules">Rules</a>
|
|
16
|
+
</nav>
|
|
17
|
+
|
|
18
|
+
<p class="lead">Short, copy-paste recipes for the things you'll actually do. For a guided
|
|
19
|
+
build-from-scratch see the <a href="#/docs/components-walkthrough">Walkthrough</a>; for the full
|
|
20
|
+
API see the <a href="#/docs/components">Reference</a>.</p>
|
|
21
|
+
|
|
22
|
+
<hr>
|
|
23
|
+
|
|
24
|
+
<h2>Create a component</h2>
|
|
25
|
+
<ol>
|
|
26
|
+
<li>Open <a href="#/components">Data → Components</a> → <strong>New Component</strong>.</li>
|
|
27
|
+
<li>Name it in lowercase-with-hyphens (e.g. <code>price-tag</code>). The name is permanent and
|
|
28
|
+
becomes the tag <code><dm-price-tag></code>.</li>
|
|
29
|
+
<li>Fill the <code><template></code>, <code><props></code> and <code><script></code>
|
|
30
|
+
tabs (the editor watches the live preview as you type).</li>
|
|
31
|
+
<li><strong>Save</strong>. It compiles first — fix any reported errors and it lands on disk.</li>
|
|
32
|
+
</ol>
|
|
33
|
+
|
|
34
|
+
<hr>
|
|
35
|
+
|
|
36
|
+
<h2>Render a value</h2>
|
|
37
|
+
<p>Interpolate a prop or state value with double braces:</p>
|
|
38
|
+
<pre class="code-block"><code><template><span class="dm-price">{{currency}}{{amount}}</span></template>
|
|
39
|
+
<props>
|
|
40
|
+
{
|
|
41
|
+
"currency": { "type": "string", "default": "£" },
|
|
42
|
+
"amount": { "type": "number", "default": 0 }
|
|
43
|
+
}
|
|
44
|
+
</props>
|
|
45
|
+
<script>export default {};</script></code></pre>
|
|
46
|
+
<p>Use it: <code>[component name="price-tag" currency="$" amount="49" /]</code></p>
|
|
47
|
+
|
|
48
|
+
<hr>
|
|
49
|
+
|
|
50
|
+
<h2>Add state and respond to a click</h2>
|
|
51
|
+
<p>Render the markup with <code>data-action</code> hooks, then attach <strong>one</strong> delegated
|
|
52
|
+
listener to the shadow root in <code>onMount</code>. Mutate state with <code>this.set()</code> — the
|
|
53
|
+
template re-renders automatically.</p>
|
|
54
|
+
<pre class="code-block"><code><template>
|
|
55
|
+
<div class="dm-counter">
|
|
56
|
+
<strong>{{count}}</strong>
|
|
57
|
+
<button data-action="inc">+</button>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
<props>{ "step": { "type": "number", "default": 1 } }</props>
|
|
61
|
+
<script>
|
|
62
|
+
export default {
|
|
63
|
+
data() { return { count: 0 }; },
|
|
64
|
+
methods: {
|
|
65
|
+
inc() { this.set({ count: this.data.count + this.props.step }); }
|
|
66
|
+
},
|
|
67
|
+
onMount() {
|
|
68
|
+
this.el.shadowRoot.addEventListener('click', (e) => {
|
|
69
|
+
if (e.target.dataset.action === 'inc') this.inc();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
</script></code></pre>
|
|
74
|
+
|
|
75
|
+
<hr>
|
|
76
|
+
|
|
77
|
+
<h2>Initialise state from a prop</h2>
|
|
78
|
+
<p><code>data()</code> runs without a <code>this</code> binding, so it can't read props. Seed the value
|
|
79
|
+
in <code>onMount()</code> instead:</p>
|
|
80
|
+
<pre class="code-block"><code>data() { return { count: 0 }; },
|
|
81
|
+
onMount() { this.set({ count: this.props.initial }); }</code></pre>
|
|
82
|
+
|
|
83
|
+
<hr>
|
|
84
|
+
|
|
85
|
+
<h2>Show something conditionally</h2>
|
|
86
|
+
<pre class="code-block"><code>{{#if showReset}}<button data-action="reset">Reset</button>{{/if}}
|
|
87
|
+
{{#unless soldOut}}<button>Buy</button>{{/unless}}</code></pre>
|
|
88
|
+
|
|
89
|
+
<hr>
|
|
90
|
+
|
|
91
|
+
<h2>Loop over a list</h2>
|
|
92
|
+
<p>Pass an array prop (attribute value is JSON) and iterate with <code>{{#each}}</code>:</p>
|
|
93
|
+
<pre class="code-block"><code><template>
|
|
94
|
+
<ul>{{#each items}}<li>{{@index}}. {{.}}</li>{{/each}}</ul>
|
|
95
|
+
</template>
|
|
96
|
+
<props>{ "items": { "type": "array", "default": [] } }</props>
|
|
97
|
+
<script>export default {};</script></code></pre>
|
|
98
|
+
<pre class="code-block"><code><dm-my-list items='["Alpha","Beta","Gamma"]'></dm-my-list></code></pre>
|
|
99
|
+
|
|
100
|
+
<hr>
|
|
101
|
+
|
|
102
|
+
<h2>Accept arbitrary inner content (slot)</h2>
|
|
103
|
+
<pre class="code-block"><code><template>
|
|
104
|
+
<div class="dm-callout"><slot></slot></div>
|
|
105
|
+
</template></code></pre>
|
|
106
|
+
<pre class="code-block"><code><dm-callout><p>Anything here is projected into the slot.</p></dm-callout></code></pre>
|
|
107
|
+
|
|
108
|
+
<hr>
|
|
109
|
+
|
|
110
|
+
<h2>Fetch data from the API</h2>
|
|
111
|
+
<p>Domma's HTTP helper <code>H</code> is available inside the script. Fetch in <code>onMount</code> and
|
|
112
|
+
push results into state:</p>
|
|
113
|
+
<pre class="code-block"><code>export default {
|
|
114
|
+
data() { return { entries: [] }; },
|
|
115
|
+
async onMount() {
|
|
116
|
+
const res = await H.get('/api/v1/announcements');
|
|
117
|
+
this.set({ entries: res.entries || [] });
|
|
118
|
+
}
|
|
119
|
+
};</code></pre>
|
|
120
|
+
|
|
121
|
+
<hr>
|
|
122
|
+
|
|
123
|
+
<h2>Run something on a timer (and clean up)</h2>
|
|
124
|
+
<p>Start the interval in <code>onMount</code>; always tear it down in <code>onUnmount</code> so it
|
|
125
|
+
doesn't leak when the element is removed:</p>
|
|
126
|
+
<pre class="code-block"><code>export default {
|
|
127
|
+
data() { return { now: '' }; },
|
|
128
|
+
_timer: null,
|
|
129
|
+
methods: { tick() { this.set({ now: new Date().toLocaleTimeString() }); } },
|
|
130
|
+
onMount() { this.tick(); this._timer = setInterval(() => this.tick(), 1000); },
|
|
131
|
+
onUnmount() { if (this._timer) clearInterval(this._timer); }
|
|
132
|
+
};</code></pre>
|
|
133
|
+
|
|
134
|
+
<hr>
|
|
135
|
+
|
|
136
|
+
<h2>Style it without leaking</h2>
|
|
137
|
+
<p>Anything in <code><style></code> is scoped to the component, so you can use short class names
|
|
138
|
+
freely:</p>
|
|
139
|
+
<pre class="code-block"><code><style>
|
|
140
|
+
.dm-callout { padding: 1rem; border-left: 3px solid var(--dm-primary, #5aa8ff); }
|
|
141
|
+
</style></code></pre>
|
|
142
|
+
|
|
143
|
+
<hr>
|
|
144
|
+
|
|
145
|
+
<h2>Use it on a page</h2>
|
|
146
|
+
<p>Either form works and auto-loads the component's module:</p>
|
|
147
|
+
<pre class="code-block"><code>[component name="counter" step="2" /]
|
|
148
|
+
|
|
149
|
+
<dm-counter step="2"></dm-counter></code></pre>
|
|
150
|
+
|
|
151
|
+
<hr>
|
|
152
|
+
|
|
153
|
+
<h2>Move a component between sites</h2>
|
|
154
|
+
<ol>
|
|
155
|
+
<li>On <a href="#/components">Components</a>, click <span data-icon="download"></span> Export on the
|
|
156
|
+
row — you'll download <code>name.dmcomponent.json</code>.</li>
|
|
157
|
+
<li>On the other site, click <strong>Import</strong> and pick that file. On a name clash you'll be
|
|
158
|
+
asked to confirm an overwrite.</li>
|
|
159
|
+
</ol>
|
|
160
|
+
|
|
161
|
+
<hr>
|
|
162
|
+
|
|
163
|
+
<h2>File it under a project</h2>
|
|
164
|
+
<p>In the editor, set the <strong>Project</strong> dropdown. That writes <code>meta.project</code> to
|
|
165
|
+
the sidecar so the component shows under that project's filtered list and is scoped accordingly.</p>
|
|
166
|
+
|
|
167
|
+
<hr>
|
|
168
|
+
<p class="text-muted" style="font-size:.9rem;">
|
|
169
|
+
Next: <a href="#/docs/components-walkthrough">Walkthrough →</a> ·
|
|
170
|
+
<a href="#/docs/components-rules">Rules & gotchas</a>
|
|
171
|
+
</p>
|
|
172
|
+
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|