domma-cms 0.101.8 → 0.101.9

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.
Files changed (32) hide show
  1. package/admin/dist/domma/domma-tools.css +3 -3
  2. package/admin/dist/domma/domma-tools.min.js +3 -3
  3. package/admin/js/templates/docs/api-actions.html +1 -1
  4. package/admin/js/templates/docs/api-authentication.html +1 -1
  5. package/admin/js/templates/docs/api-builder.html +1 -1
  6. package/admin/js/templates/docs/api-collections.html +1 -1
  7. package/admin/js/templates/docs/api-external.html +1 -1
  8. package/admin/js/templates/docs/api-forms.html +1 -1
  9. package/admin/js/templates/docs/api-layouts.html +1 -1
  10. package/admin/js/templates/docs/api-media.html +1 -1
  11. package/admin/js/templates/docs/api-navigation.html +1 -1
  12. package/admin/js/templates/docs/api-pages.html +1 -1
  13. package/admin/js/templates/docs/api-plugins.html +1 -1
  14. package/admin/js/templates/docs/api-scaffold.html +1 -1
  15. package/admin/js/templates/docs/api-settings.html +1 -1
  16. package/admin/js/templates/docs/api-users.html +1 -1
  17. package/admin/js/templates/docs/api-views.html +1 -1
  18. package/admin/js/templates/docs/usage-cta-shortcode.html +2 -2
  19. package/package.json +2 -2
  20. package/plugins/blog/blocks/blog-card-row.html +1 -1
  21. package/plugins/blog/blocks/blog-card.html +1 -1
  22. package/plugins/blog/blocks/blog-post-classic.html +1 -1
  23. package/plugins/blog/blocks/blog-post-feature.html +1 -1
  24. package/plugins/blog/blocks/blog-post-minimal.html +1 -1
  25. package/plugins/blog/blocks/blog-post-sidebar.html +1 -1
  26. package/plugins/blog/blocks/blog-post-split.html +1 -1
  27. package/plugins/blog/plugin.public.js +2 -2
  28. package/plugins/free-tier.lock.json +10 -10
  29. package/plugins/shopping-cart/plugin.public.js +1 -1
  30. package/server/routes/docs-public.js +3 -3
  31. package/server/routes/public.js +4 -4
  32. package/server/server.js +10 -0
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools CSS v0.50.3
2
+ * Domma Tools CSS v0.51.0
3
3
  * Dynamic Object Manipulation & Modeling API
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-09-28T16:16:38.439Z
6
- * Commit: 6399d99
5
+ * Built: 2026-09-29T07:41:00.052Z
6
+ * Commit: f264aeb
7
7
  */
8
8
 
9
9
  /*!
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools v0.50.3
2
+ * Domma Tools v0.51.0
3
3
  * Developer tools: Theme Roller & Page Roller
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-09-28T16:16:25.037Z
6
- * Commit: 6399d99
5
+ * Built: 2026-09-29T07:40:46.433Z
6
+ * Commit: f264aeb
7
7
  *
8
8
  * Requires: domma.min.js
9
9
  */
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Actions API</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Actions API</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Authentication</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Authentication</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · API Builder</h1>
2
+ <h1><span data-icon="code"></span> API &middot; API Builder</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Collections</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Collections</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · External API</h1>
2
+ <h1><span data-icon="code"></span> API &middot; External API</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Forms API</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Forms API</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Layouts</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Layouts</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Media</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Media</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Navigation</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Navigation</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Pages</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Pages</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Plugins</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Plugins</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Scaffold API</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Scaffold API</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Settings</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Settings</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Users</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Users</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -1,5 +1,5 @@
1
1
  <div class="view-header">
2
- <h1><span data-icon="code"></span> API · Views API</h1>
2
+ <h1><span data-icon="code"></span> API &middot; Views API</h1>
3
3
  <a href="#/api-reference" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> API overview</a>
4
4
  </div>
5
5
  <style>
@@ -51,7 +51,7 @@
51
51
  <td><code>style</code></td>
52
52
  <td>No</td>
53
53
  <td><code>"primary"</code></td>
54
- <td>primary · secondary · ghost · danger</td>
54
+ <td>primary &middot; secondary &middot; ghost &middot; danger</td>
55
55
  </tr>
56
56
  <tr>
57
57
  <td><code>icon</code></td>
@@ -63,7 +63,7 @@
63
63
  <td><code>size</code></td>
64
64
  <td>No</td>
65
65
  <td>-</td>
66
- <td>sm · md · lg</td>
66
+ <td>sm &middot; md &middot; lg</td>
67
67
  </tr>
68
68
  <tr>
69
69
  <td><code>confirm</code></td>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.101.8",
3
+ "version": "0.101.9",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -84,7 +84,7 @@
84
84
  "@fastify/rate-limit": "^10.3.0",
85
85
  "@fastify/static": "9.1.1",
86
86
  "bcryptjs": "^3.0.3",
87
- "domma-js": "0.50.3",
87
+ "domma-js": "0.51.0",
88
88
  "dotenv": "^17.2.3",
89
89
  "fastify": "5.8.5",
90
90
  "gray-matter": "^4.0.3",
@@ -4,7 +4,7 @@
4
4
  -->
5
5
  <a class="br-media {{imageClass}}" href="{{url}}" tabindex="-1" aria-hidden="true">{{{image}}}</a>
6
6
  <div class="br-body">
7
- <p class="br-meta"><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
7
+ <p class="br-meta"><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
8
8
  <h2 class="br-title"><a href="{{url}}">{{title}}</a></h2>
9
9
  <p class="br-excerpt">{{excerpt}}</p>
10
10
  <div class="br-foot"><span class="br-cats">{{{categories}}}</span><a class="br-author" href="{{authorUrl}}">{{authorName}}</a></div>
@@ -12,5 +12,5 @@
12
12
  <div class="bc-kicker">{{{categories}}}</div>
13
13
  <h2 class="bc-title"><a href="{{url}}">{{title}}</a></h2>
14
14
  <p class="bc-excerpt">{{excerpt}}</p>
15
- <p class="bc-meta"><a href="{{authorUrl}}">{{authorName}}</a> · <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
15
+ <p class="bc-meta"><a href="{{authorUrl}}">{{authorName}}</a> &middot; <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
16
16
  </div>
@@ -14,7 +14,7 @@
14
14
  <div class="pc-kicker">{{{categories}}}</div>
15
15
  <h1 class="pc-title">{{title}}</h1>
16
16
  <p class="pc-standfirst">{{excerpt}}</p>
17
- <p class="pc-meta">By <a href="{{authorUrl}}">{{authorName}}</a> · <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
17
+ <p class="pc-meta">By <a href="{{authorUrl}}">{{authorName}}</a> &middot; <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
18
18
  </header>
19
19
  <figure class="pc-figure {{imageClass}}">{{{image}}}</figure>
20
20
  <div class="pc-body blog-content">{{{content}}}</div>
@@ -14,7 +14,7 @@
14
14
  <div class="pf-kicker">{{{categories}}}</div>
15
15
  <h1 class="pf-title">{{title}}</h1>
16
16
  <p class="pf-standfirst">{{excerpt}}</p>
17
- <p class="pf-meta"><a href="{{authorUrl}}">{{authorName}}</a> · <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
17
+ <p class="pf-meta"><a href="{{authorUrl}}">{{authorName}}</a> &middot; <time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
18
18
  </div>
19
19
  </header>
20
20
  <div class="pf-wrap">
@@ -9,7 +9,7 @@
9
9
  -->
10
10
  <article class="pm">
11
11
  <header class="pm-head">
12
- <p class="pm-meta"><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
12
+ <p class="pm-meta"><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
13
13
  <h1 class="pm-title">{{title}}</h1>
14
14
  <p class="pm-by">by <a href="{{authorUrl}}">{{authorName}}</a></p>
15
15
  </header>
@@ -25,7 +25,7 @@
25
25
  <p class="ps-label">Written by</p>
26
26
  <p class="ps-author"><a href="{{authorUrl}}">{{authorName}}</a></p>
27
27
  <p class="ps-label">Published</p>
28
- <p><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
28
+ <p><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
29
29
  <div class="ps-tags">{{{tags}}}</div>
30
30
  {{{share}}}
31
31
  </div>
@@ -16,7 +16,7 @@
16
16
  <div class="pt-kicker">{{{categories}}}</div>
17
17
  <h1 class="pt-title">{{title}}</h1>
18
18
  <p class="pt-standfirst">{{excerpt}}</p>
19
- <p class="pt-meta"><a href="{{authorUrl}}">{{authorName}}</a><br><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> · {{readingTime}}</p>
19
+ <p class="pt-meta"><a href="{{authorUrl}}">{{authorName}}</a><br><time datetime="{{publishedAt}}">{{publishedAt:DD/MM/YYYY}}</time> &middot; {{readingTime}}</p>
20
20
  </div>
21
21
  </header>
22
22
  <div class="pt-wrap">
@@ -133,7 +133,7 @@ ${items || ''}
133
133
  },
134
134
  {title, urlPath: request.url.split('?')[0], baseUrl: originOf(request)}
135
135
  );
136
- return reply.type('text/html').send(html);
136
+ return reply.type('text/html; charset=utf-8').send(html);
137
137
  }
138
138
 
139
139
  /** One handler per page; `matchBlogPath()` (lib/layouts.js) picks which. */
@@ -231,7 +231,7 @@ ${items}
231
231
  item: {type: 'blog:post', id: post.id}
232
232
  }
233
233
  );
234
- return reply.type('text/html').send(html);
234
+ return reply.type('text/html; charset=utf-8').send(html);
235
235
  }
236
236
  };
237
237
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "note": "Written by `make sync-free-tier` in dcms-marketplace. Do not edit these plugins here - edit them there and sync.",
3
- "source": "dcms-marketplace@5dcb9db",
3
+ "source": "dcms-marketplace@45a0ace",
4
4
  "plugins": {
5
5
  "blog": {
6
6
  "version": "1.9.3",
@@ -22,21 +22,21 @@
22
22
  "blocks/blog-card-compact.css": "eb8e8ae9a6461c362bc021ea0396c6b4481047b6b36a50eeb59e47375e49be75",
23
23
  "blocks/blog-card-compact.html": "7e1e31923808e6108d57d34309d3442b5d93b4aa4396f0e471e870249934ab8f",
24
24
  "blocks/blog-card-row.css": "f5f0249c51a40cc71ff8d054bfa887353badc79730b0f323c76ac32f7d893b44",
25
- "blocks/blog-card-row.html": "da6c25793afc14381dd8aa70cd01b859a1dcce20e859770f6b3b24cea4b0f868",
25
+ "blocks/blog-card-row.html": "c08148ab5b5addc86f1148841c21ffb11f9a1b94f28fbdc08bc9650167920374",
26
26
  "blocks/blog-card.css": "d480a80a7378bd55659e9549bb30d8c106bdae1a9cd450a8c0114bfaefa94cd2",
27
- "blocks/blog-card.html": "60a45772f7642856fe462dc1ef7e15f6b85a451d9b1a307af7ba814f0a6b1e73",
27
+ "blocks/blog-card.html": "61e222fe36c6fe9167f2e0b5750a9ddb724c90c0c4719d4a671197baf525f52f",
28
28
  "blocks/blog-post-classic.css": "9acac617847b5a10dabd5efccb601572e2751f792c4038475325f11e24142584",
29
- "blocks/blog-post-classic.html": "380c55a638645ca5bf5810836bcf9327d050123b513007027012f451f2d16558",
29
+ "blocks/blog-post-classic.html": "3fb19c554108e34c6602d2b01715067a2e181544ff206193dd9116482d0d852b",
30
30
  "blocks/blog-post-essay.css": "d1842443927ea7c93772a06ade69fc834099e6820d99767e0aab8e80981a3784",
31
31
  "blocks/blog-post-essay.html": "85bb2eafd07192f97fdf7a9690e80aab266edd43277d64fa0e91ebbc37c2d243",
32
32
  "blocks/blog-post-feature.css": "7d8a242b627f3826df21d5470e643b386967122ea3585e69f96cce9ffc2452e9",
33
- "blocks/blog-post-feature.html": "d34cb682f2285f6688a86b539e76f9d2229db4cce61ef0200cf5940148983a80",
33
+ "blocks/blog-post-feature.html": "7b38c8f50c7c31db2506abc672856cad6ac522597ae9ce551320083e910d053e",
34
34
  "blocks/blog-post-minimal.css": "2323b84a7d0a99e1c85c30faa18bb2ee16763ff08cfe87f181ffadf8cff84dd0",
35
- "blocks/blog-post-minimal.html": "b0fa2e33389b186fc6834e839a424ef0b3122d13e31219cb91dd58ccd82021b9",
35
+ "blocks/blog-post-minimal.html": "71e3c461b5f8ac7f72055810bae611045aac84148dcf8f227a9d526bfecf0b49",
36
36
  "blocks/blog-post-sidebar.css": "3c66109fa48ca448605eb5acd0c998f43649fb3747813b4a811992ec89886007",
37
- "blocks/blog-post-sidebar.html": "2fb6e1348c4800ae5cea08db02fc842916e16f64ab91ef6a20a47f7db310bba1",
37
+ "blocks/blog-post-sidebar.html": "1e18f334950617cb38d5fb22c7d9ba29d76b744a28e761997c9f4e03de10187b",
38
38
  "blocks/blog-post-split.css": "61ae29850d6c324ffbfc2110a917d5980ad17b0635870656ccd885d20ac9ec86",
39
- "blocks/blog-post-split.html": "1b5fa63eee45b06d62757c96bd069645ace69e3b5a6e145ba1612626b2e4c7a2",
39
+ "blocks/blog-post-split.html": "65e291e203bf7b9c7e07e705a56605d5a4c316781bdcbb0cb8c537d0b8281478",
40
40
  "collections/categories/schema.json": "0728e522ed8e3c623fd45e137cd03e0960aa5158e70613ec8628c8b3928e0929",
41
41
  "collections/comments/schema.json": "29cbd289cbfcfc4158c9357a218db4ce06eddc3370bfbf9afb7d7f52065a849e",
42
42
  "collections/posts/schema.json": "19a5a770d3eefea73f4117454bd1312a7a7e3d727cfcdfde141139fbc81cabd7",
@@ -51,7 +51,7 @@
51
51
  "lib/samples.js": "e8cdb77ce22e43937707f6854d3a5cb722437dc584e22ac7b24480bef686cf85",
52
52
  "plugin.js": "0aa46a01b5298065497f39729b88e34a9f56ac684b0a14f8d94ab6007f0221d0",
53
53
  "plugin.json": "f74ba12c5dfef7d0b9accd267cc0c4e333ea61a936cc8afa565db82da5b02389",
54
- "plugin.public.js": "ab23fadd5655fab8eea0e636a3e1f8ced78432d6da929059a4a147f0805ff3a4",
54
+ "plugin.public.js": "67e109da613237d9ec52e7cc2f3442bb61ca5a51cb184448b9f0d993e50f7ae3",
55
55
  "public/blog.css": "fd733e6b624190da1e70dc1b6613e2376efd7f7570a28fab311d81006d03d960",
56
56
  "public/blog.js": "d01c72a9834dcc7c5241923371f2b164ac30d20ac9f1a5c5dc5b71fa3cb09f08",
57
57
  "public/samples/aurora.svg": "feb29e2b46465913fc29de9eda221b00b1541f1fa3ea7120f0c1d1cd11a9a4f6",
@@ -157,7 +157,7 @@
157
157
  "lib/summary.js": "a510386e28eb19d9617f488551d1057819ae9f76fc85c744f2e5dbce492607fe",
158
158
  "plugin.js": "594c2174e36566f90ac1e9ff11cb76656ad111dc392dc8e9556d731b7dd0a41a",
159
159
  "plugin.json": "1d76fea187dea958b82e752e5d6e7a729217b08b061490c8590e2c6e6537496e",
160
- "plugin.public.js": "176383dfa7af913c433e0397584c46a67a21ab3307b80a08f0aaf50da2634f9e",
160
+ "plugin.public.js": "ad901378eb28758d18b9f257e4a76d5a453f89fb984ed02a10f62f1a13cd2a62",
161
161
  "public/cart.js": "fcc706773a36e4bfd02ea235bcdad629137e996218e0a0e23892e33c689f2a6e",
162
162
  "public/lib/money.js": "d22b2b6f80dde6f7887b69e24188c05ea93c02217aba639b5331aa3bd616f4fc",
163
163
  "public/lib/orders.js": "09478e20d238adc8e387b3d9efcb13c441898fab18387ce6a1a8a92304ac4c5b",
@@ -55,7 +55,7 @@ export default async function shopPublic(fastify) {
55
55
  title, description, ogImage: image, urlPath, noindex,
56
56
  baseUrl: `${request.protocol}://${request.host || request.headers.host || request.hostname}`
57
57
  });
58
- return reply.type('text/html').header('Cache-Control', 'no-store').send(html);
58
+ return reply.type('text/html; charset=utf-8').header('Cache-Control', 'no-store').send(html);
59
59
  }
60
60
 
61
61
  const pages = {
@@ -26,16 +26,16 @@ export async function docsPublicRoutes(fastify) {
26
26
  // Hard kill-switch: docs disabled → 404 the entire surface.
27
27
  if (!(await isProjectEnabled(DOCS_PROJECT_SLUG))) {
28
28
  reply.status(404);
29
- return reply.type('text/html').send(await render404(request.url.split('?')[0]));
29
+ return reply.type('text/html; charset=utf-8').send(await render404(request.url.split('?')[0]));
30
30
  }
31
31
 
32
32
  const docPath = String(request.params['*'] || '');
33
33
  const html = await renderDocPage(docPath, {user: null, baseUrl: getBaseUrl(request)});
34
34
  if (html == null) {
35
35
  reply.status(404);
36
- return reply.type('text/html').send(await render404(`/domma-docs${docPath ? '/' + docPath : ''}`));
36
+ return reply.type('text/html; charset=utf-8').send(await render404(`/domma-docs${docPath ? '/' + docPath : ''}`));
37
37
  }
38
- return reply.type('text/html').send(html);
38
+ return reply.type('text/html; charset=utf-8').send(html);
39
39
  };
40
40
 
41
41
  fastify.get('/domma-docs', handle); // exact - params['*'] === '' (home landing)
@@ -87,7 +87,7 @@ function sendHtml(request, reply, html) {
87
87
  // Deliberately no reply.code() here: the 404 handlers have already called
88
88
  // reply.status(404), and forcing 200 would turn every not-found into a
89
89
  // soft 404 that search engines index as a real page.
90
- return reply.type('text/html').send(html);
90
+ return reply.type('text/html; charset=utf-8').send(html);
91
91
  }
92
92
 
93
93
  export async function publicRoutes(fastify) {
@@ -170,7 +170,7 @@ export async function publicRoutes(fastify) {
170
170
  shared: {expiresAt: link.expiresAt, label: link.label}
171
171
  }
172
172
  });
173
- return reply.type('text/html').send(html);
173
+ return reply.type('text/html; charset=utf-8').send(html);
174
174
  });
175
175
 
176
176
  // Public pages catch-all
@@ -246,7 +246,7 @@ export async function publicRoutes(fastify) {
246
246
 
247
247
  if (!isPublic && !checkVisibility(userObj, vis)) {
248
248
  reply.status(403);
249
- return reply.type('text/html').send(accessDeniedHtml(urlPath));
249
+ return reply.type('text/html; charset=utf-8').send(accessDeniedHtml(urlPath));
250
250
  }
251
251
 
252
252
  const baseUrl = getBaseUrl(request);
@@ -270,7 +270,7 @@ export async function publicRoutes(fastify) {
270
270
  if (isDraft) {
271
271
  reply.header('cache-control', 'no-store, private');
272
272
  reply.header('x-robots-tag', 'noindex, nofollow');
273
- return reply.type('text/html').send(await render());
273
+ return reply.type('text/html; charset=utf-8').send(await render());
274
274
  }
275
275
 
276
276
  // A row-scoped [view] shows each signed-in user their OWN rows, so the
package/server/server.js CHANGED
@@ -164,6 +164,16 @@ await app.register(compress, {
164
164
  encodings: ['br', 'gzip', 'deflate']
165
165
  });
166
166
 
167
+ // Ensure any text/html responses always declare charset=utf-8 to prevent browsers,
168
+ // proxies, and scrapers from falling back to ISO-8859-1 or Windows-1252.
169
+ app.addHook('onSend', async (request, reply, payload) => {
170
+ const contentType = reply.getHeader('content-type');
171
+ if (typeof contentType === 'string' && contentType.startsWith('text/html') && !contentType.toLowerCase().includes('charset')) {
172
+ reply.header('content-type', `${contentType}; charset=utf-8`);
173
+ }
174
+ return payload;
175
+ });
176
+
167
177
  // ---------------------------------------------------------------------------
168
178
  // Static Assets
169
179
  // ---------------------------------------------------------------------------