domma-cms 0.25.15 → 0.25.16
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/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/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/tutorials.js +1 -1
- package/config/menus/admin-sidebar.json +76 -6
- package/package.json +1 -1
- package/server/server.js +3 -1
- package/server/services/sidebar-migration.js +145 -3
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="component"></span> Components — Reference</h1>
|
|
3
|
+
<a href="#/components" class="btn btn-ghost btn-sm"><span data-icon="component"></span> Manage Components</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">
|
|
19
|
+
Components are small, reusable custom elements you author in one file and drop into any page.
|
|
20
|
+
Each one is a single <code>.dmc</code> file with up to four sections — <code><template></code>,
|
|
21
|
+
<code><props></code>, <code><script></code> and <code><style></code> — and the CMS
|
|
22
|
+
compiles it to a self-contained browser module that registers a
|
|
23
|
+
<code><dm-<em>name</em>></code> custom element. No build step, no framework wiring.
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p>Manage them under <a href="#/components">Data → Components</a>. This page is the complete
|
|
27
|
+
reference; for guided learning start with the
|
|
28
|
+
<a href="#/docs/components-walkthrough">Walkthrough</a> or the
|
|
29
|
+
<a href="#/docs/components-howto">How-To</a> recipes.</p>
|
|
30
|
+
|
|
31
|
+
<hr>
|
|
32
|
+
|
|
33
|
+
<h2>The <code>.dmc</code> file format</h2>
|
|
34
|
+
|
|
35
|
+
<p>A component is a single file made of named blocks, much like a single-file component in other
|
|
36
|
+
frameworks. Three blocks are <strong>required</strong> (<code><template></code>,
|
|
37
|
+
<code><props></code>, <code><script></code>); <code><style></code> is optional.
|
|
38
|
+
Block order does not matter.</p>
|
|
39
|
+
|
|
40
|
+
<pre class="code-block"><code><template>
|
|
41
|
+
<div class="dm-greeting">Hello, {{name}}!</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<props>
|
|
45
|
+
{
|
|
46
|
+
"name": { "type": "string", "default": "world", "label": "Name to greet" }
|
|
47
|
+
}
|
|
48
|
+
</props>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
export default {};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.dm-greeting { font-weight: 600; }
|
|
56
|
+
</style></code></pre>
|
|
57
|
+
|
|
58
|
+
<p>Saving compiles the source <em>before</em> it is written to disk — a malformed component never
|
|
59
|
+
reaches the filesystem. The compiler reports precise, per-block errors (see
|
|
60
|
+
<a href="#/docs/components-rules">Rules</a> for every error code).</p>
|
|
61
|
+
|
|
62
|
+
<hr>
|
|
63
|
+
|
|
64
|
+
<h2><code><template></code> — markup</h2>
|
|
65
|
+
|
|
66
|
+
<p>Plain HTML plus a Handlebars-style expression syntax. Data comes from the component's
|
|
67
|
+
<code>props</code> and reactive <code>data()</code> state. Supported syntax:</p>
|
|
68
|
+
|
|
69
|
+
<table class="table table-sm">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr><th>Syntax</th><th>Purpose</th></tr>
|
|
72
|
+
</thead>
|
|
73
|
+
<tbody>
|
|
74
|
+
<tr><td><code>{{ value }}</code></td><td>Interpolate a prop or state value (HTML-escaped).</td></tr>
|
|
75
|
+
<tr><td><code>{{#if cond}}…{{/if}}</code></td><td>Render the block only when <code>cond</code> is truthy.</td></tr>
|
|
76
|
+
<tr><td><code>{{#unless cond}}…{{/unless}}</code></td><td>Render the block only when <code>cond</code> is falsy.</td></tr>
|
|
77
|
+
<tr><td><code>{{#each list}}…{{/each}}</code></td><td>Repeat the block for each item. Inside, <code>{{.}}</code> is the item (for primitives), <code>{{@index}}</code>, <code>{{@first}}</code> and <code>{{@last}}</code> are available; object items expose their own keys.</td></tr>
|
|
78
|
+
<tr><td><code>{{#with obj}}…{{/with}}</code></td><td>Scope the block to an object's keys.</td></tr>
|
|
79
|
+
<tr><td><code><slot></slot></code></td><td>Project the light-DOM content placed between the element's tags.</td></tr>
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
|
|
83
|
+
<pre class="code-block"><code><template>
|
|
84
|
+
<ul class="dm-list">
|
|
85
|
+
{{#each items}}
|
|
86
|
+
<li class="{{#if @first}}first{{/if}}">{{@index}}. {{.}}</li>
|
|
87
|
+
{{/each}}
|
|
88
|
+
</ul>
|
|
89
|
+
{{#if title}}<h4>{{title}}</h4>{{/if}}
|
|
90
|
+
<slot></slot>
|
|
91
|
+
</template></code></pre>
|
|
92
|
+
|
|
93
|
+
<hr>
|
|
94
|
+
|
|
95
|
+
<h2><code><props></code> — typed inputs</h2>
|
|
96
|
+
|
|
97
|
+
<p>A JSON <strong>object</strong> (not an array). Each key is a prop name; each value declares a
|
|
98
|
+
<code>type</code> and may add a <code>default</code> and a <code>label</code> (shown in the editor's
|
|
99
|
+
preview panel). The type is validated at compile time.</p>
|
|
100
|
+
|
|
101
|
+
<p>Valid types: <code>string</code>, <code>number</code>, <code>boolean</code>, <code>array</code>,
|
|
102
|
+
<code>object</code>.</p>
|
|
103
|
+
|
|
104
|
+
<pre class="code-block"><code>{
|
|
105
|
+
"initial": { "type": "number", "default": 0, "label": "Initial value" },
|
|
106
|
+
"step": { "type": "number", "default": 1, "label": "Step size" },
|
|
107
|
+
"showReset": { "type": "boolean", "default": false, "label": "Show reset button" },
|
|
108
|
+
"tags": { "type": "array", "default": [], "label": "Tag list" }
|
|
109
|
+
}</code></pre>
|
|
110
|
+
|
|
111
|
+
<p>Props are passed as HTML attributes where the value is always a string; the runtime coerces it to
|
|
112
|
+
the declared type (so <code>step="2"</code> arrives as the number <code>2</code>). Read props inside
|
|
113
|
+
<code>onMount()</code> and methods via <code>this.props</code> — see the script API below.</p>
|
|
114
|
+
|
|
115
|
+
<hr>
|
|
116
|
+
|
|
117
|
+
<h2><code><script></code> — behaviour</h2>
|
|
118
|
+
|
|
119
|
+
<p>An ES module with a single <code>export default { … }</code> object describing the component. The
|
|
120
|
+
export is the only requirement; every key inside it is optional.</p>
|
|
121
|
+
|
|
122
|
+
<pre class="code-block"><code>export default {
|
|
123
|
+
// Reactive state. Called WITHOUT `this` — do not read props here.
|
|
124
|
+
data() { return { count: 0 }; },
|
|
125
|
+
|
|
126
|
+
// Methods are bound to the component context (`this`).
|
|
127
|
+
methods: {
|
|
128
|
+
inc() { this.set({ count: this.data.count + this.props.step }); }
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Lifecycle — bound to `this`; props and the element are available here.
|
|
132
|
+
onMount() { this.set({ count: this.props.initial }); },
|
|
133
|
+
onUnmount() { /* clean up timers, listeners, etc. */ }
|
|
134
|
+
};</code></pre>
|
|
135
|
+
|
|
136
|
+
<table class="table table-sm">
|
|
137
|
+
<thead>
|
|
138
|
+
<tr><th>Key</th><th>What it is</th></tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
<tr><td><code>data()</code></td><td>Returns the initial reactive state object. Called with no <code>this</code> binding — <strong>props are not available here</strong>; initialise from props in <code>onMount()</code>.</td></tr>
|
|
142
|
+
<tr><td><code>methods</code></td><td>Object of functions bound to the component context.</td></tr>
|
|
143
|
+
<tr><td><code>onMount()</code></td><td>Lifecycle hook fired when the element is attached to the DOM. Props and the element node are available.</td></tr>
|
|
144
|
+
<tr><td><code>onUnmount()</code></td><td>Lifecycle hook fired when the element is removed — clear intervals/listeners here.</td></tr>
|
|
145
|
+
</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
|
|
148
|
+
<p><strong>Inside methods and lifecycle hooks</strong>, <code>this</code> exposes:</p>
|
|
149
|
+
|
|
150
|
+
<table class="table table-sm">
|
|
151
|
+
<thead>
|
|
152
|
+
<tr><th>Member</th><th>Description</th></tr>
|
|
153
|
+
</thead>
|
|
154
|
+
<tbody>
|
|
155
|
+
<tr><td><code>this.props</code></td><td>The (type-coerced) props the element was used with.</td></tr>
|
|
156
|
+
<tr><td><code>this.data</code></td><td>The current reactive state snapshot.</td></tr>
|
|
157
|
+
<tr><td><code>this.set({ … })</code></td><td>Patch reactive state; the template re-renders with the new values.</td></tr>
|
|
158
|
+
<tr><td><code>this.el</code></td><td>The custom-element DOM node. Its rendered markup lives in <code>this.el.shadowRoot</code> — attach event listeners there.</td></tr>
|
|
159
|
+
</tbody>
|
|
160
|
+
</table>
|
|
161
|
+
|
|
162
|
+
<p>Domma's browser globals (<code>$</code>, <code>H</code>, <code>S</code>, <code>E</code>, <code>D</code>,
|
|
163
|
+
<code>_</code>, …) are available inside the script just as on any page, so a component can fetch data,
|
|
164
|
+
read storage, or pop a toast.</p>
|
|
165
|
+
|
|
166
|
+
<hr>
|
|
167
|
+
|
|
168
|
+
<h2><code><style></code> — scoped CSS</h2>
|
|
169
|
+
|
|
170
|
+
<p>Optional. CSS here is scoped to the component's shadow DOM, so selectors can't leak out and the
|
|
171
|
+
page's styles can't leak in. By convention components prefix their root class with
|
|
172
|
+
<code>dm-<em>name</em></code> for clarity, but scoping is automatic either way.</p>
|
|
173
|
+
|
|
174
|
+
<pre class="code-block"><code><style>
|
|
175
|
+
.dm-counter { text-align: center; padding: 1rem; }
|
|
176
|
+
.dm-counter .value { font-size: 2.5rem; font-weight: 700; }
|
|
177
|
+
</style></code></pre>
|
|
178
|
+
|
|
179
|
+
<hr>
|
|
180
|
+
|
|
181
|
+
<h2>Using a component on a page</h2>
|
|
182
|
+
|
|
183
|
+
<p>Two equivalent ways, both server-rendered into the page so the right component module is auto-injected:</p>
|
|
184
|
+
|
|
185
|
+
<h3>Shortcode (Markdown)</h3>
|
|
186
|
+
<pre class="code-block"><code>[component name="counter" initial="10" step="2" showReset="true" /]</code></pre>
|
|
187
|
+
|
|
188
|
+
<h3>Custom element (HTML)</h3>
|
|
189
|
+
<pre class="code-block"><code><dm-counter initial="10" step="2" show-reset="true"></dm-counter></code></pre>
|
|
190
|
+
|
|
191
|
+
<p>Both pass <code>{initial, step, showReset}</code> to the component. Note the attribute casing:
|
|
192
|
+
camelCase props map to <code>kebab-case</code> attributes on the raw tag
|
|
193
|
+
(<code>showReset</code> → <code>show-reset</code>); the shortcode accepts the prop name as written.
|
|
194
|
+
When the page is rendered the server collects every <code>dm-*</code> tag used and injects one
|
|
195
|
+
<code><script type="module" src="/api/components/<em>name</em>.js"></code> at the end of the
|
|
196
|
+
body, which registers the element via <code>Domma.component()</code>.</p>
|
|
197
|
+
|
|
198
|
+
<p>To project content into a component's <code><slot></code>, use the custom-element form with
|
|
199
|
+
children:</p>
|
|
200
|
+
<pre class="code-block"><code><dm-reveal-panel heading="Click to reveal">
|
|
201
|
+
<p>Hidden content appears here when toggled.</p>
|
|
202
|
+
</dm-reveal-panel></code></pre>
|
|
203
|
+
|
|
204
|
+
<hr>
|
|
205
|
+
|
|
206
|
+
<h2>Storage, REST API & bundles</h2>
|
|
207
|
+
|
|
208
|
+
<p>Source files live in <code>content/components/<em>name</em>.dmc</code>, with an optional sidecar
|
|
209
|
+
<code>content/components/<em>name</em>.meta.json</code> holding the <code>bundled</code> flag and
|
|
210
|
+
<code>project</code> tag. The REST surface (all under <code>/api/components</code>):</p>
|
|
211
|
+
|
|
212
|
+
<table class="table table-sm">
|
|
213
|
+
<thead>
|
|
214
|
+
<tr><th>Method & path</th><th>Auth</th><th>Purpose</th></tr>
|
|
215
|
+
</thead>
|
|
216
|
+
<tbody>
|
|
217
|
+
<tr><td><code>GET /api/components</code></td><td>read</td><td>List components with metadata + parsed props.</td></tr>
|
|
218
|
+
<tr><td><code>GET /api/components/:name</code></td><td>read</td><td>Raw <code>.dmc</code> source + parsed props.</td></tr>
|
|
219
|
+
<tr><td><code>GET /api/components/:name.js</code></td><td>public</td><td>Compiled browser module (what pages load).</td></tr>
|
|
220
|
+
<tr><td><code>POST /api/components/compile</code></td><td>update</td><td>Transient compile for the live preview (nothing written).</td></tr>
|
|
221
|
+
<tr><td><code>PUT /api/components/:name</code></td><td>update</td><td>Create or update (compiles first; errors returned, file untouched on failure).</td></tr>
|
|
222
|
+
<tr><td><code>DELETE /api/components/:name</code></td><td>delete</td><td>Delete source + sidecar.</td></tr>
|
|
223
|
+
<tr><td><code>GET /api/components/:name/export</code></td><td>read</td><td>Download a <code>.dmcomponent.json</code> bundle.</td></tr>
|
|
224
|
+
<tr><td><code>POST /api/components/import</code></td><td>update</td><td>Import a bundle (<code>?overwrite=true</code> to replace).</td></tr>
|
|
225
|
+
</tbody>
|
|
226
|
+
</table>
|
|
227
|
+
|
|
228
|
+
<p>A bundle is a small JSON envelope you can move between sites (Export / Import buttons on the
|
|
229
|
+
<a href="#/components">Components</a> page):</p>
|
|
230
|
+
<pre class="code-block"><code>{ "format": 1, "name": "counter", "source": "<template>…", "bundled": false }</code></pre>
|
|
231
|
+
|
|
232
|
+
<h3>Bundled & plugin components</h3>
|
|
233
|
+
<p>The <code>bundled</code> flag marks a component as part of fresh-install seed data. Plugins may
|
|
234
|
+
also contribute components via the <code>registerComponent()</code> hook — these appear in the list
|
|
235
|
+
with a <span class="badge badge-secondary">plugin</span> badge, are served from memory, and are
|
|
236
|
+
<strong>read-only</strong> from the admin (you can't edit or delete them, and you can't save a
|
|
237
|
+
component whose name a plugin already owns).</p>
|
|
238
|
+
|
|
239
|
+
<hr>
|
|
240
|
+
|
|
241
|
+
<h2>The five seeded starter components</h2>
|
|
242
|
+
<p>Fresh installs ship with these (never overwritten if you've edited them) — open any in
|
|
243
|
+
<a href="#/components">Components</a> to read the full source:</p>
|
|
244
|
+
<ul>
|
|
245
|
+
<li><code>counter</code> — state, methods and lifecycle in one small example</li>
|
|
246
|
+
<li><code>copy-button</code> — an async method using the clipboard API</li>
|
|
247
|
+
<li><code>reveal-panel</code> — conditional rendering plus a <code><slot></code></li>
|
|
248
|
+
<li><code>testimonial-card</code> — presentational only (empty <code>export default {}</code>)</li>
|
|
249
|
+
<li><code>countdown-timer</code> — an interval started in <code>onMount</code> and cleared in <code>onUnmount</code></li>
|
|
250
|
+
</ul>
|
|
251
|
+
|
|
252
|
+
<hr>
|
|
253
|
+
<p class="text-muted" style="font-size:.9rem;">
|
|
254
|
+
Next: <a href="#/docs/components-howto">How-To recipes →</a>
|
|
255
|
+
</p>
|
|
256
|
+
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="component"></span> Components — Rules</h1>
|
|
3
|
+
<a href="#/docs/components" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> Reference</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">The constraints the compiler enforces, and the handful of behaviours that commonly
|
|
19
|
+
trip people up. Everything here is validated at save time — a component that breaks a rule never
|
|
20
|
+
reaches disk.</p>
|
|
21
|
+
|
|
22
|
+
<hr>
|
|
23
|
+
|
|
24
|
+
<h2>Naming</h2>
|
|
25
|
+
<ul>
|
|
26
|
+
<li>Names must match <code>^[a-z][a-z0-9-]*$</code> — start with a lowercase letter, then lowercase
|
|
27
|
+
letters, digits and hyphens.</li>
|
|
28
|
+
<li>Valid: <code>counter</code>, <code>copy-button</code>, <code>price-tag-2</code>. Invalid:
|
|
29
|
+
<code>Counter</code>, <code>1counter</code>, <code>my_component</code>, <code>../etc/passwd</code>.</li>
|
|
30
|
+
<li>The name becomes the element tag <code><dm-<em>name</em>></code> and is <strong>permanent</strong> —
|
|
31
|
+
it can't be changed after creation. To rename, create a new component and delete the old one.</li>
|
|
32
|
+
</ul>
|
|
33
|
+
|
|
34
|
+
<hr>
|
|
35
|
+
|
|
36
|
+
<h2>Required blocks</h2>
|
|
37
|
+
<ul>
|
|
38
|
+
<li><code><template></code>, <code><props></code> and <code><script></code> are
|
|
39
|
+
required. A missing one fails with <code>MISSING_BLOCK</code>.</li>
|
|
40
|
+
<li><code><style></code> is optional and defaults to empty.</li>
|
|
41
|
+
<li>Blocks are matched by their tags and may appear in any order.</li>
|
|
42
|
+
</ul>
|
|
43
|
+
|
|
44
|
+
<hr>
|
|
45
|
+
|
|
46
|
+
<h2>Props must be a typed JSON object</h2>
|
|
47
|
+
<ul>
|
|
48
|
+
<li>The <code><props></code> body must be valid JSON and an <strong>object</strong> (not an
|
|
49
|
+
array, not a bare value) — otherwise <code>INVALID_PROPS_JSON</code>.</li>
|
|
50
|
+
<li>Every prop must declare a <code>type</code> that is one of <code>string</code>,
|
|
51
|
+
<code>number</code>, <code>boolean</code>, <code>array</code>, <code>object</code>. Anything else
|
|
52
|
+
(or a missing type) fails validation.</li>
|
|
53
|
+
<li><code>default</code> and <code>label</code> are optional. <code>label</code> only affects the
|
|
54
|
+
editor's preview panel.</li>
|
|
55
|
+
</ul>
|
|
56
|
+
|
|
57
|
+
<hr>
|
|
58
|
+
|
|
59
|
+
<h2>The script must <code>export default</code> an object</h2>
|
|
60
|
+
<ul>
|
|
61
|
+
<li>The <code><script></code> body must be exactly one <code>export default { … };</code>
|
|
62
|
+
statement — the compiler strips that prefix and embeds the object. No <code>export default</code>
|
|
63
|
+
means <code>SCRIPT_MISSING_EXPORT</code>.</li>
|
|
64
|
+
<li>Use an object expression — not a class, not multiple exports. An empty
|
|
65
|
+
<code>export default {};</code> is valid for a purely presentational component.</li>
|
|
66
|
+
</ul>
|
|
67
|
+
|
|
68
|
+
<hr>
|
|
69
|
+
|
|
70
|
+
<h2>Gotchas worth memorising</h2>
|
|
71
|
+
|
|
72
|
+
<div class="alert alert-warning">
|
|
73
|
+
<strong>1. <code>data()</code> can't see props.</strong> It's called with no <code>this</code>
|
|
74
|
+
binding, so <code>this.props</code> is unavailable there. Initialise prop-derived state inside
|
|
75
|
+
<code>onMount()</code> (which <em>is</em> bound) using <code>this.set(...)</code>.
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="alert alert-warning">
|
|
79
|
+
<strong>2. Boolean coercion is string-based.</strong> Attribute values are strings. A boolean prop
|
|
80
|
+
is true only for the string <code>"true"</code>; <code>showReset="false"</code> is still a non-empty
|
|
81
|
+
string and coerces to <code>true</code>. To switch a boolean off, set it to <code>"true"</code> or
|
|
82
|
+
omit the attribute entirely.
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="alert alert-warning">
|
|
86
|
+
<strong>3. Attribute casing.</strong> On the raw custom element, camelCase props are written as
|
|
87
|
+
<code>kebab-case</code> attributes — <code>showReset</code> becomes <code>show-reset</code>. The
|
|
88
|
+
<code>[component]</code> shortcode accepts the prop name as written.
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="alert alert-warning">
|
|
92
|
+
<strong>4. Array / object props are JSON in the attribute.</strong> Pass them as a JSON string —
|
|
93
|
+
e.g. <code>items='["a","b"]'</code> — and the runtime parses them to the declared type.
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="alert alert-warning">
|
|
97
|
+
<strong>5. Listen on the shadow root.</strong> A component's rendered markup lives in
|
|
98
|
+
<code>this.el.shadowRoot</code>. Attach delegated listeners there (one listener, switch on
|
|
99
|
+
<code>data-action</code>) rather than to <code>document</code>.
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="alert alert-warning">
|
|
103
|
+
<strong>6. Clean up in <code>onUnmount()</code>.</strong> Anything you start in <code>onMount</code>
|
|
104
|
+
— intervals, timeouts, external listeners — must be torn down in <code>onUnmount</code> so it
|
|
105
|
+
doesn't leak when the element is removed.
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<hr>
|
|
109
|
+
|
|
110
|
+
<h2>Plugin-owned components are read-only</h2>
|
|
111
|
+
<ul>
|
|
112
|
+
<li>Components contributed by a plugin show a <span class="badge badge-secondary">plugin</span>
|
|
113
|
+
badge, are served from memory, and <strong>cannot be edited or deleted</strong> from the admin
|
|
114
|
+
(attempting it returns <code>PLUGIN_OWNED</code>).</li>
|
|
115
|
+
<li>You also can't save a disk component whose name a plugin already owns — pick a different name.</li>
|
|
116
|
+
</ul>
|
|
117
|
+
|
|
118
|
+
<hr>
|
|
119
|
+
|
|
120
|
+
<h2>Import / export constraints</h2>
|
|
121
|
+
<ul>
|
|
122
|
+
<li>A bundle must have a numeric <code>format</code> no newer than the CMS supports, plus
|
|
123
|
+
<code>name</code> and <code>source</code> strings — otherwise <code>INVALID_BUNDLE</code>.</li>
|
|
124
|
+
<li>Importing over an existing name returns <code>CONFLICT</code> unless you confirm the overwrite
|
|
125
|
+
(the Import button prompts you).</li>
|
|
126
|
+
<li>Imported source is compiled like any save, so a broken bundle is rejected with the same errors.</li>
|
|
127
|
+
</ul>
|
|
128
|
+
|
|
129
|
+
<hr>
|
|
130
|
+
|
|
131
|
+
<h2>Rendering & safety notes</h2>
|
|
132
|
+
<ul>
|
|
133
|
+
<li><code>{{ }}</code> interpolation is HTML-escaped, so prop values can't inject markup.</li>
|
|
134
|
+
<li>The markdown sanitiser keeps a live allowlist of <code>dm-*</code> tags, refreshed whenever you
|
|
135
|
+
save or delete a component — a component you just created is immediately usable in page content.
|
|
136
|
+
<code>on*</code> handler attributes and <code>javascript:</code> URLs are still stripped globally.</li>
|
|
137
|
+
<li>Saving recompiles and invalidates the cached module, so edits take effect on the next page load
|
|
138
|
+
without a server restart.</li>
|
|
139
|
+
</ul>
|
|
140
|
+
|
|
141
|
+
<hr>
|
|
142
|
+
|
|
143
|
+
<h2>Compiler error codes at a glance</h2>
|
|
144
|
+
<table class="table table-sm">
|
|
145
|
+
<thead>
|
|
146
|
+
<tr><th>Code</th><th>Meaning</th></tr>
|
|
147
|
+
</thead>
|
|
148
|
+
<tbody>
|
|
149
|
+
<tr><td><code>INVALID_NAME</code></td><td>Name doesn't match the naming rule.</td></tr>
|
|
150
|
+
<tr><td><code>MISSING_BLOCK</code></td><td>A required <code><template></code> / <code><props></code> / <code><script></code> block is absent.</td></tr>
|
|
151
|
+
<tr><td><code>INVALID_PROPS_JSON</code></td><td>Props aren't a JSON object, or a prop has an invalid/missing type.</td></tr>
|
|
152
|
+
<tr><td><code>SCRIPT_MISSING_EXPORT</code></td><td>The script has no <code>export default</code> object.</td></tr>
|
|
153
|
+
<tr><td><code>PLUGIN_OWNED</code></td><td>Tried to edit/delete (or shadow) a plugin-contributed component.</td></tr>
|
|
154
|
+
<tr><td><code>INVALID_BUNDLE</code> / <code>CONFLICT</code></td><td>Import bundle malformed, or a name collision needs overwrite confirmation.</td></tr>
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
|
|
158
|
+
<hr>
|
|
159
|
+
<p class="text-muted" style="font-size:.9rem;">
|
|
160
|
+
Back to <a href="#/docs/components">Reference</a> ·
|
|
161
|
+
<a href="#/docs/components-howto">How-To</a> ·
|
|
162
|
+
<a href="#/docs/components-walkthrough">Walkthrough</a>
|
|
163
|
+
</p>
|
|
164
|
+
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="component"></span> Components — Walkthrough</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">
|
|
19
|
+
We'll build one component end-to-end: a <strong>star rating</strong> widget you can drop onto any
|
|
20
|
+
page with <code>[component name="star-rating" max="5" value="3" /]</code>. By the end you'll have
|
|
21
|
+
used every section of a <code>.dmc</code> file, reactive state, an event listener, a custom event,
|
|
22
|
+
and the live preview. Follow along in a real <strong>New Component</strong> editor.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<hr>
|
|
26
|
+
|
|
27
|
+
<h2>Step 1 — Create the component</h2>
|
|
28
|
+
<p>Open <a href="#/components">Data → Components</a> → <strong>New Component</strong> and name it
|
|
29
|
+
<code>star-rating</code>. Remember: lowercase + hyphens, and the name is permanent because it
|
|
30
|
+
becomes the element tag <code><dm-star-rating></code>. The four source tabs
|
|
31
|
+
(<code><template></code>, <code><props></code>, <code><script></code>,
|
|
32
|
+
<code><style></code>) are on the left; a live preview is on the right.</p>
|
|
33
|
+
|
|
34
|
+
<hr>
|
|
35
|
+
|
|
36
|
+
<h2>Step 2 — Declare the props</h2>
|
|
37
|
+
<p>Start with the inputs, because they shape everything else. A rating needs a maximum number of
|
|
38
|
+
stars and a current value. Put this in the <code><props></code> tab:</p>
|
|
39
|
+
<pre class="code-block"><code>{
|
|
40
|
+
"max": { "type": "number", "default": 5, "label": "Number of stars" },
|
|
41
|
+
"value": { "type": "number", "default": 0, "label": "Current rating" },
|
|
42
|
+
"readonly": { "type": "boolean", "default": false, "label": "Read-only (display only)" }
|
|
43
|
+
}</code></pre>
|
|
44
|
+
<p>These appear in the preview's <em>Preview props</em> panel, so you can poke values as you build.</p>
|
|
45
|
+
|
|
46
|
+
<hr>
|
|
47
|
+
|
|
48
|
+
<h2>Step 3 — Write the template</h2>
|
|
49
|
+
<p>We render <code>max</code> stars and mark each as filled if its index is below the current rating.
|
|
50
|
+
Because <code>{{#each}}</code> needs a list, we'll build a <code>stars</code> array in state
|
|
51
|
+
(next step) where each item knows whether it's <code>on</code>. For now, the markup:</p>
|
|
52
|
+
<pre class="code-block"><code><template>
|
|
53
|
+
<div class="dm-stars" role="img" aria-label="{{value}} of {{max}}">
|
|
54
|
+
{{#each stars}}
|
|
55
|
+
<button class="star {{#if on}}on{{/if}}" data-action="rate" data-index="{{n}}">★</button>
|
|
56
|
+
{{/each}}
|
|
57
|
+
</div>
|
|
58
|
+
</template></code></pre>
|
|
59
|
+
<p>Each star is a button carrying its 1-based position in <code>data-index</code>, so a single click
|
|
60
|
+
handler can read which star was pressed.</p>
|
|
61
|
+
|
|
62
|
+
<hr>
|
|
63
|
+
|
|
64
|
+
<h2>Step 4 — Add the script (state + behaviour)</h2>
|
|
65
|
+
<p>Three jobs: build the <code>stars</code> array from <code>value</code>, rebuild it whenever the
|
|
66
|
+
rating changes, and handle clicks. Remember <code>data()</code> can't see props, so we seed state
|
|
67
|
+
in <code>onMount()</code>.</p>
|
|
68
|
+
<pre class="code-block"><code><script>
|
|
69
|
+
export default {
|
|
70
|
+
data() { return { stars: [], value: 0 }; },
|
|
71
|
+
|
|
72
|
+
methods: {
|
|
73
|
+
// Build the star list for a given rating.
|
|
74
|
+
render(value) {
|
|
75
|
+
const max = this.props.max;
|
|
76
|
+
const stars = [];
|
|
77
|
+
for (let n = 1; n <= max; n++) stars.push({ n, on: n <= value });
|
|
78
|
+
this.set({ stars, value });
|
|
79
|
+
},
|
|
80
|
+
rate(n) {
|
|
81
|
+
if (this.props.readonly) return;
|
|
82
|
+
this.render(n);
|
|
83
|
+
// Tell the outside world the rating changed.
|
|
84
|
+
this.el.dispatchEvent(new CustomEvent('rating-change', {
|
|
85
|
+
detail: { value: n }, bubbles: true
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
onMount() {
|
|
91
|
+
this.render(this.props.value);
|
|
92
|
+
this.el.shadowRoot.addEventListener('click', (e) => {
|
|
93
|
+
const btn = e.target.closest('[data-action="rate"]');
|
|
94
|
+
if (btn) this.rate(Number(btn.dataset.index));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
</script></code></pre>
|
|
99
|
+
<p>Note the patterns from the <a href="#/docs/components">Reference</a>: one delegated listener on
|
|
100
|
+
<code>shadowRoot</code>, <code>this.set()</code> to re-render, <code>this.props</code> for inputs,
|
|
101
|
+
and a bubbling <code>CustomEvent</code> so a page can react to the chosen rating.</p>
|
|
102
|
+
|
|
103
|
+
<hr>
|
|
104
|
+
|
|
105
|
+
<h2>Step 5 — Style it (scoped)</h2>
|
|
106
|
+
<pre class="code-block"><code><style>
|
|
107
|
+
.dm-stars { display: inline-flex; gap: .15rem; }
|
|
108
|
+
.dm-stars .star {
|
|
109
|
+
background: none; border: 0; cursor: pointer;
|
|
110
|
+
font-size: 1.5rem; line-height: 1; color: #d0d0d0; padding: 0;
|
|
111
|
+
}
|
|
112
|
+
.dm-stars .star.on { color: #f5b301; }
|
|
113
|
+
</style></code></pre>
|
|
114
|
+
<p>These selectors are scoped to the component, so <code>.star</code> won't collide with anything else
|
|
115
|
+
on the page.</p>
|
|
116
|
+
|
|
117
|
+
<hr>
|
|
118
|
+
|
|
119
|
+
<h2>Step 6 — Watch the preview, then save</h2>
|
|
120
|
+
<p>As you typed, the editor recompiled and re-mounted the component in the preview iframe. Toggle
|
|
121
|
+
<code>readonly</code> and change <code>value</code> in the <em>Preview props</em> panel to sanity-check
|
|
122
|
+
both modes. When it looks right, hit <strong>Save Component</strong> — the source compiles before it
|
|
123
|
+
is written, so a typo surfaces as a clear error rather than a broken page.</p>
|
|
124
|
+
|
|
125
|
+
<hr>
|
|
126
|
+
|
|
127
|
+
<h2>Step 7 — Use it on a page</h2>
|
|
128
|
+
<p>Display-only, in any Markdown page:</p>
|
|
129
|
+
<pre class="code-block"><code>[component name="star-rating" max="5" value="4" readonly="true" /]</code></pre>
|
|
130
|
+
<p>Interactive, reacting to the custom event with a little page script:</p>
|
|
131
|
+
<pre class="code-block"><code><dm-star-rating max="5" value="0" id="r1"></dm-star-rating>
|
|
132
|
+
<script>
|
|
133
|
+
document.getElementById('r1').addEventListener('rating-change', (e) => {
|
|
134
|
+
console.log('User picked', e.detail.value);
|
|
135
|
+
});
|
|
136
|
+
</script></code></pre>
|
|
137
|
+
|
|
138
|
+
<hr>
|
|
139
|
+
|
|
140
|
+
<h2>Step 8 — Share it</h2>
|
|
141
|
+
<p>Back on <a href="#/components">Components</a>, the <span data-icon="download"></span> Export button
|
|
142
|
+
gives you <code>star-rating.dmcomponent.json</code> — import that on another Domma site to reuse the
|
|
143
|
+
widget verbatim.</p>
|
|
144
|
+
|
|
145
|
+
<hr>
|
|
146
|
+
|
|
147
|
+
<h2>What you used</h2>
|
|
148
|
+
<ul>
|
|
149
|
+
<li><strong>Props</strong> with types + defaults, coerced from attributes</li>
|
|
150
|
+
<li><strong>Template</strong> interpolation, <code>{{#each}}</code> and <code>{{#if}}</code></li>
|
|
151
|
+
<li><strong>State</strong> via <code>data()</code> + <code>this.set()</code>, seeded from props in <code>onMount()</code></li>
|
|
152
|
+
<li>A single delegated <strong>shadow-root listener</strong> and <code>data-*</code> dispatch</li>
|
|
153
|
+
<li>A bubbling <strong>CustomEvent</strong> for page-level integration</li>
|
|
154
|
+
<li><strong>Scoped CSS</strong>, the live preview, and export/import</li>
|
|
155
|
+
</ul>
|
|
156
|
+
|
|
157
|
+
<p>For the rules that keep components well-behaved — naming, the <code>data()</code>-can't-see-props
|
|
158
|
+
gotcha, boolean coercion, plugin-owned names — read <a href="#/docs/components-rules">Rules</a>.</p>
|
|
159
|
+
|
|
160
|
+
<hr>
|
|
161
|
+
<p class="text-muted" style="font-size:.9rem;">
|
|
162
|
+
Next: <a href="#/docs/components-rules">Rules & gotchas →</a>
|
|
163
|
+
</p>
|
|
164
|
+
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|