@geekity/cms 0.3.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.
Files changed (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +870 -0
  3. package/admin/layouts/_flash.njk +5 -0
  4. package/admin/layouts/base.njk +27 -0
  5. package/admin/layouts/dashboard.njk +78 -0
  6. package/admin/layouts/document-conflict.njk +49 -0
  7. package/admin/layouts/document-editor.njk +167 -0
  8. package/admin/layouts/document-list.njk +92 -0
  9. package/admin/layouts/federation.njk +145 -0
  10. package/admin/layouts/login.njk +23 -0
  11. package/admin/layouts/placeholder.njk +10 -0
  12. package/admin/layouts/settings.njk +116 -0
  13. package/admin/layouts/setup.njk +27 -0
  14. package/admin/layouts/shell.njk +37 -0
  15. package/admin/layouts/users.njk +91 -0
  16. package/admin/static/admin.css +726 -0
  17. package/admin/static/editor.js +30 -0
  18. package/dist/admin/assets.d.ts +26 -0
  19. package/dist/admin/assets.d.ts.map +1 -0
  20. package/dist/admin/assets.js +38 -0
  21. package/dist/admin/assets.js.map +1 -0
  22. package/dist/admin/credentials.d.ts +28 -0
  23. package/dist/admin/credentials.d.ts.map +1 -0
  24. package/dist/admin/credentials.js +39 -0
  25. package/dist/admin/credentials.js.map +1 -0
  26. package/dist/admin/documents.d.ts +134 -0
  27. package/dist/admin/documents.d.ts.map +1 -0
  28. package/dist/admin/documents.js +613 -0
  29. package/dist/admin/documents.js.map +1 -0
  30. package/dist/admin/federation.d.ts +194 -0
  31. package/dist/admin/federation.d.ts.map +1 -0
  32. package/dist/admin/federation.js +299 -0
  33. package/dist/admin/federation.js.map +1 -0
  34. package/dist/admin/flash.d.ts +24 -0
  35. package/dist/admin/flash.d.ts.map +1 -0
  36. package/dist/admin/flash.js +31 -0
  37. package/dist/admin/flash.js.map +1 -0
  38. package/dist/admin/index.d.ts +23 -0
  39. package/dist/admin/index.d.ts.map +1 -0
  40. package/dist/admin/index.js +15 -0
  41. package/dist/admin/index.js.map +1 -0
  42. package/dist/admin/passwords.d.ts +49 -0
  43. package/dist/admin/passwords.d.ts.map +1 -0
  44. package/dist/admin/passwords.js +122 -0
  45. package/dist/admin/passwords.js.map +1 -0
  46. package/dist/admin/preview.d.ts +18 -0
  47. package/dist/admin/preview.d.ts.map +1 -0
  48. package/dist/admin/preview.js +83 -0
  49. package/dist/admin/preview.js.map +1 -0
  50. package/dist/admin/routes.d.ts +54 -0
  51. package/dist/admin/routes.d.ts.map +1 -0
  52. package/dist/admin/routes.js +383 -0
  53. package/dist/admin/routes.js.map +1 -0
  54. package/dist/admin/session.d.ts +44 -0
  55. package/dist/admin/session.d.ts.map +1 -0
  56. package/dist/admin/session.js +69 -0
  57. package/dist/admin/session.js.map +1 -0
  58. package/dist/admin/settings.d.ts +194 -0
  59. package/dist/admin/settings.d.ts.map +1 -0
  60. package/dist/admin/settings.js +470 -0
  61. package/dist/admin/settings.js.map +1 -0
  62. package/dist/admin/store.d.ts +407 -0
  63. package/dist/admin/store.d.ts.map +1 -0
  64. package/dist/admin/store.js +704 -0
  65. package/dist/admin/store.js.map +1 -0
  66. package/dist/admin/templates.d.ts +36 -0
  67. package/dist/admin/templates.d.ts.map +1 -0
  68. package/dist/admin/templates.js +43 -0
  69. package/dist/admin/templates.js.map +1 -0
  70. package/dist/admin/uploads.d.ts +96 -0
  71. package/dist/admin/uploads.d.ts.map +1 -0
  72. package/dist/admin/uploads.js +179 -0
  73. package/dist/admin/uploads.js.map +1 -0
  74. package/dist/admin/users.d.ts +95 -0
  75. package/dist/admin/users.d.ts.map +1 -0
  76. package/dist/admin/users.js +233 -0
  77. package/dist/admin/users.js.map +1 -0
  78. package/dist/cli.d.ts +85 -0
  79. package/dist/cli.d.ts.map +1 -0
  80. package/dist/cli.js +424 -0
  81. package/dist/cli.js.map +1 -0
  82. package/dist/config.d.ts +172 -0
  83. package/dist/config.d.ts.map +1 -0
  84. package/dist/config.js +184 -0
  85. package/dist/config.js.map +1 -0
  86. package/dist/content/document.d.ts +66 -0
  87. package/dist/content/document.d.ts.map +1 -0
  88. package/dist/content/document.js +13 -0
  89. package/dist/content/document.js.map +1 -0
  90. package/dist/content/index.d.ts +17 -0
  91. package/dist/content/index.d.ts.map +1 -0
  92. package/dist/content/index.js +10 -0
  93. package/dist/content/index.js.map +1 -0
  94. package/dist/content/markdown.d.ts +3 -0
  95. package/dist/content/markdown.d.ts.map +1 -0
  96. package/dist/content/markdown.js +39 -0
  97. package/dist/content/markdown.js.map +1 -0
  98. package/dist/content/media.d.ts +65 -0
  99. package/dist/content/media.d.ts.map +1 -0
  100. package/dist/content/media.js +143 -0
  101. package/dist/content/media.js.map +1 -0
  102. package/dist/content/parser.d.ts +24 -0
  103. package/dist/content/parser.d.ts.map +1 -0
  104. package/dist/content/parser.js +165 -0
  105. package/dist/content/parser.js.map +1 -0
  106. package/dist/content/save.d.ts +60 -0
  107. package/dist/content/save.d.ts.map +1 -0
  108. package/dist/content/save.js +103 -0
  109. package/dist/content/save.js.map +1 -0
  110. package/dist/content/slug.d.ts +25 -0
  111. package/dist/content/slug.d.ts.map +1 -0
  112. package/dist/content/slug.js +75 -0
  113. package/dist/content/slug.js.map +1 -0
  114. package/dist/content/store.d.ts +133 -0
  115. package/dist/content/store.d.ts.map +1 -0
  116. package/dist/content/store.js +411 -0
  117. package/dist/content/store.js.map +1 -0
  118. package/dist/content/sync.d.ts +146 -0
  119. package/dist/content/sync.d.ts.map +1 -0
  120. package/dist/content/sync.js +421 -0
  121. package/dist/content/sync.js.map +1 -0
  122. package/dist/content/writer.d.ts +37 -0
  123. package/dist/content/writer.d.ts.map +1 -0
  124. package/dist/content/writer.js +97 -0
  125. package/dist/content/writer.js.map +1 -0
  126. package/dist/env.d.ts +48 -0
  127. package/dist/env.d.ts.map +1 -0
  128. package/dist/env.js +2 -0
  129. package/dist/env.js.map +1 -0
  130. package/dist/federation/actor.d.ts +55 -0
  131. package/dist/federation/actor.d.ts.map +1 -0
  132. package/dist/federation/actor.js +101 -0
  133. package/dist/federation/actor.js.map +1 -0
  134. package/dist/federation/article.d.ts +83 -0
  135. package/dist/federation/article.d.ts.map +1 -0
  136. package/dist/federation/article.js +169 -0
  137. package/dist/federation/article.js.map +1 -0
  138. package/dist/federation/delivery.d.ts +90 -0
  139. package/dist/federation/delivery.d.ts.map +1 -0
  140. package/dist/federation/delivery.js +289 -0
  141. package/dist/federation/delivery.js.map +1 -0
  142. package/dist/federation/federation.d.ts +75 -0
  143. package/dist/federation/federation.d.ts.map +1 -0
  144. package/dist/federation/federation.js +190 -0
  145. package/dist/federation/federation.js.map +1 -0
  146. package/dist/federation/followers.d.ts +43 -0
  147. package/dist/federation/followers.d.ts.map +1 -0
  148. package/dist/federation/followers.js +69 -0
  149. package/dist/federation/followers.js.map +1 -0
  150. package/dist/federation/inbox.d.ts +68 -0
  151. package/dist/federation/inbox.d.ts.map +1 -0
  152. package/dist/federation/inbox.js +171 -0
  153. package/dist/federation/inbox.js.map +1 -0
  154. package/dist/federation/index.d.ts +14 -0
  155. package/dist/federation/index.d.ts.map +1 -0
  156. package/dist/federation/index.js +10 -0
  157. package/dist/federation/index.js.map +1 -0
  158. package/dist/federation/keys.d.ts +27 -0
  159. package/dist/federation/keys.d.ts.map +1 -0
  160. package/dist/federation/keys.js +60 -0
  161. package/dist/federation/keys.js.map +1 -0
  162. package/dist/federation/mount.d.ts +23 -0
  163. package/dist/federation/mount.d.ts.map +1 -0
  164. package/dist/federation/mount.js +71 -0
  165. package/dist/federation/mount.js.map +1 -0
  166. package/dist/federation/paths.d.ts +85 -0
  167. package/dist/federation/paths.d.ts.map +1 -0
  168. package/dist/federation/paths.js +97 -0
  169. package/dist/federation/paths.js.map +1 -0
  170. package/dist/index.d.ts +106 -0
  171. package/dist/index.d.ts.map +1 -0
  172. package/dist/index.js +148 -0
  173. package/dist/index.js.map +1 -0
  174. package/dist/init.d.ts +71 -0
  175. package/dist/init.d.ts.map +1 -0
  176. package/dist/init.js +125 -0
  177. package/dist/init.js.map +1 -0
  178. package/dist/web/assets.d.ts +65 -0
  179. package/dist/web/assets.d.ts.map +1 -0
  180. package/dist/web/assets.js +152 -0
  181. package/dist/web/assets.js.map +1 -0
  182. package/dist/web/context.d.ts +121 -0
  183. package/dist/web/context.d.ts.map +1 -0
  184. package/dist/web/context.js +112 -0
  185. package/dist/web/context.js.map +1 -0
  186. package/dist/web/documents.d.ts +28 -0
  187. package/dist/web/documents.d.ts.map +1 -0
  188. package/dist/web/documents.js +39 -0
  189. package/dist/web/documents.js.map +1 -0
  190. package/dist/web/feeds.d.ts +122 -0
  191. package/dist/web/feeds.d.ts.map +1 -0
  192. package/dist/web/feeds.js +229 -0
  193. package/dist/web/feeds.js.map +1 -0
  194. package/dist/web/index.d.ts +17 -0
  195. package/dist/web/index.d.ts.map +1 -0
  196. package/dist/web/index.js +10 -0
  197. package/dist/web/index.js.map +1 -0
  198. package/dist/web/negotiate.d.ts +219 -0
  199. package/dist/web/negotiate.d.ts.map +1 -0
  200. package/dist/web/negotiate.js +409 -0
  201. package/dist/web/negotiate.js.map +1 -0
  202. package/dist/web/pagination.d.ts +48 -0
  203. package/dist/web/pagination.d.ts.map +1 -0
  204. package/dist/web/pagination.js +30 -0
  205. package/dist/web/pagination.js.map +1 -0
  206. package/dist/web/render.d.ts +72 -0
  207. package/dist/web/render.d.ts.map +1 -0
  208. package/dist/web/render.js +73 -0
  209. package/dist/web/render.js.map +1 -0
  210. package/dist/web/routes.d.ts +31 -0
  211. package/dist/web/routes.d.ts.map +1 -0
  212. package/dist/web/routes.js +366 -0
  213. package/dist/web/routes.js.map +1 -0
  214. package/dist/web/templates.d.ts +34 -0
  215. package/dist/web/templates.d.ts.map +1 -0
  216. package/dist/web/templates.js +106 -0
  217. package/dist/web/templates.js.map +1 -0
  218. package/package.json +68 -0
  219. package/templates/site/content/_data/site.json +8 -0
  220. package/templates/site/content/pages/about.md +11 -0
  221. package/templates/site/content/pages/pages.json +3 -0
  222. package/templates/site/content/posts/2026-01-01-hello-world.md +34 -0
  223. package/templates/site/content/posts/posts.json +4 -0
  224. package/templates/site/geekity.config.ts +11 -0
  225. package/templates/site/gitignore +3 -0
  226. package/templates/site/server.ts +20 -0
  227. package/templates/site/tsconfig.json +18 -0
  228. package/themes/default/README.md +148 -0
  229. package/themes/default/layouts/404.njk +9 -0
  230. package/themes/default/layouts/base.njk +59 -0
  231. package/themes/default/layouts/home.njk +9 -0
  232. package/themes/default/layouts/page.njk +13 -0
  233. package/themes/default/layouts/post.njk +30 -0
  234. package/themes/default/layouts/tag.njk +19 -0
  235. package/themes/default/partials/pagination.njk +14 -0
  236. package/themes/default/partials/post-list.njk +24 -0
  237. package/themes/default/partials/tags.njk +12 -0
  238. package/themes/default/static/style.css +327 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Argon2id parameters, close to the OWASP baseline for a server that also
3
+ * serves pages: 19 MiB of memory, two passes, one lane.
4
+ *
5
+ * They are written into every hash (see {@link hashPassword}), so raising them
6
+ * later does not invalidate a password stored under the old ones.
7
+ */
8
+ export declare const ARGON2_PARAMETERS: {
9
+ /** Memory cost in KiB. */
10
+ readonly memory: 19456;
11
+ /** Time cost: how many passes over that memory. */
12
+ readonly passes: 2;
13
+ /** Lanes. One, because a web request has no cores to spare. */
14
+ readonly parallelism: 1;
15
+ /** Length of the derived tag, in bytes. */
16
+ readonly tagLength: 32;
17
+ /** Salt length, in bytes. */
18
+ readonly saltLength: 16;
19
+ /**
20
+ * The Argon2 version the encoding records. 19 is 0x13, the current one, and
21
+ * the only one `node:crypto` computes: it has no version parameter. The
22
+ * number is written into the hash so a reader can see which it is, and a
23
+ * hash claiming any other version is refused rather than mis-verified.
24
+ */
25
+ readonly version: 19;
26
+ };
27
+ /**
28
+ * Hash a password with argon2id, encoded as a PHC string:
29
+ *
30
+ * ```
31
+ * $argon2id$v=19$m=19456,t=2,p=1$<salt>$<tag>
32
+ * ```
33
+ *
34
+ * The salt and the cost parameters travel with the hash, so
35
+ * {@link verifyPasswordHash} needs nothing but the stored string, and a later
36
+ * change to {@link ARGON2_PARAMETERS} leaves existing passwords verifiable.
37
+ *
38
+ * Node 24 hashes natively, so there is no argon2 dependency to build.
39
+ */
40
+ export declare function hashPassword(password: string): string;
41
+ /**
42
+ * Whether `password` is the one `encoded` was made from.
43
+ *
44
+ * A hash this function cannot parse is a `false`, not a throw: a corrupted row
45
+ * must fail the login rather than take the request down. The comparison is
46
+ * constant time.
47
+ */
48
+ export declare function verifyPasswordHash(encoded: string, password: string): boolean;
49
+ //# sourceMappingURL=passwords.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwords.d.ts","sourceRoot":"","sources":["../../src/admin/passwords.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;IAC5B,0BAA0B;aAC1B,MAAM,EAAE,KAAK;IACb,mDAAmD;aACnD,MAAM,EAAE,CAAC;IACT,+DAA+D;aAC/D,WAAW,EAAE,CAAC;IACd,2CAA2C;aAC3C,SAAS,EAAE,EAAE;IACb,6BAA6B;aAC7B,UAAU,EAAE,EAAE;IACd;;;;;OAKG;aACH,OAAO,EAAE,EAAE;CACH,CAAC;AAEX;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAY7E"}
@@ -0,0 +1,122 @@
1
+ import { argon2Sync, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * Argon2id parameters, close to the OWASP baseline for a server that also
4
+ * serves pages: 19 MiB of memory, two passes, one lane.
5
+ *
6
+ * They are written into every hash (see {@link hashPassword}), so raising them
7
+ * later does not invalidate a password stored under the old ones.
8
+ */
9
+ export const ARGON2_PARAMETERS = {
10
+ /** Memory cost in KiB. */
11
+ memory: 19456,
12
+ /** Time cost: how many passes over that memory. */
13
+ passes: 2,
14
+ /** Lanes. One, because a web request has no cores to spare. */
15
+ parallelism: 1,
16
+ /** Length of the derived tag, in bytes. */
17
+ tagLength: 32,
18
+ /** Salt length, in bytes. */
19
+ saltLength: 16,
20
+ /**
21
+ * The Argon2 version the encoding records. 19 is 0x13, the current one, and
22
+ * the only one `node:crypto` computes: it has no version parameter. The
23
+ * number is written into the hash so a reader can see which it is, and a
24
+ * hash claiming any other version is refused rather than mis-verified.
25
+ */
26
+ version: 19,
27
+ };
28
+ /**
29
+ * Hash a password with argon2id, encoded as a PHC string:
30
+ *
31
+ * ```
32
+ * $argon2id$v=19$m=19456,t=2,p=1$<salt>$<tag>
33
+ * ```
34
+ *
35
+ * The salt and the cost parameters travel with the hash, so
36
+ * {@link verifyPasswordHash} needs nothing but the stored string, and a later
37
+ * change to {@link ARGON2_PARAMETERS} leaves existing passwords verifiable.
38
+ *
39
+ * Node 24 hashes natively, so there is no argon2 dependency to build.
40
+ */
41
+ export function hashPassword(password) {
42
+ const salt = randomBytes(ARGON2_PARAMETERS.saltLength);
43
+ const tag = derive(password, salt, {
44
+ memory: ARGON2_PARAMETERS.memory,
45
+ passes: ARGON2_PARAMETERS.passes,
46
+ parallelism: ARGON2_PARAMETERS.parallelism,
47
+ tagLength: ARGON2_PARAMETERS.tagLength,
48
+ });
49
+ const parameters = `m=${String(ARGON2_PARAMETERS.memory)},t=${String(ARGON2_PARAMETERS.passes)},p=${String(ARGON2_PARAMETERS.parallelism)}`;
50
+ return `$argon2id$v=${String(ARGON2_PARAMETERS.version)}$${parameters}$${base64(salt)}$${base64(tag)}`;
51
+ }
52
+ /**
53
+ * Whether `password` is the one `encoded` was made from.
54
+ *
55
+ * A hash this function cannot parse is a `false`, not a throw: a corrupted row
56
+ * must fail the login rather than take the request down. The comparison is
57
+ * constant time.
58
+ */
59
+ export function verifyPasswordHash(encoded, password) {
60
+ const parsed = parsePhc(encoded);
61
+ if (parsed === undefined)
62
+ return false;
63
+ const tag = derive(password, parsed.salt, {
64
+ memory: parsed.memory,
65
+ passes: parsed.passes,
66
+ parallelism: parsed.parallelism,
67
+ tagLength: parsed.tag.length,
68
+ });
69
+ return tag.length === parsed.tag.length && timingSafeEqual(tag, parsed.tag);
70
+ }
71
+ function derive(password, salt, parameters) {
72
+ return Buffer.from(argon2Sync('argon2id', {
73
+ message: Buffer.from(password, 'utf8'),
74
+ nonce: salt,
75
+ memory: parameters.memory,
76
+ passes: parameters.passes,
77
+ parallelism: parameters.parallelism,
78
+ tagLength: parameters.tagLength,
79
+ }));
80
+ }
81
+ /** The PHC string back apart, or `undefined` when it is not one of ours. */
82
+ function parsePhc(encoded) {
83
+ const parts = encoded.split('$');
84
+ // ['', 'argon2id', 'v=19', 'm=..,t=..,p=..', salt, tag]
85
+ if (parts.length !== 6 || parts[0] !== '' || parts[1] !== 'argon2id')
86
+ return undefined;
87
+ // Only 0x13 is computable here, so any other version is unverifiable rather
88
+ // than merely unusual.
89
+ if (parts[2] !== `v=${String(ARGON2_PARAMETERS.version)}`)
90
+ return undefined;
91
+ const costs = new Map((parts[3] ?? '').split(',').map((pair) => {
92
+ const [key, value] = pair.split('=');
93
+ return [key ?? '', Number(value)];
94
+ }));
95
+ const memory = costs.get('m');
96
+ const passes = costs.get('t');
97
+ const parallelism = costs.get('p');
98
+ if (!isPositiveInteger(memory) || !isPositiveInteger(passes) || !isPositiveInteger(parallelism)) {
99
+ return undefined;
100
+ }
101
+ const salt = fromBase64(parts[4]);
102
+ const tag = fromBase64(parts[5]);
103
+ if (salt === undefined || tag === undefined || salt.length === 0 || tag.length === 0) {
104
+ return undefined;
105
+ }
106
+ return { memory, passes, parallelism, tagLength: tag.length, salt, tag };
107
+ }
108
+ function isPositiveInteger(value) {
109
+ return value !== undefined && Number.isInteger(value) && value > 0;
110
+ }
111
+ /** PHC uses unpadded standard base64. */
112
+ function base64(value) {
113
+ return value.toString('base64').replace(/=+$/, '');
114
+ }
115
+ function fromBase64(value) {
116
+ if (value === undefined || value === '')
117
+ return undefined;
118
+ if (!/^[A-Za-z0-9+/]+$/.test(value))
119
+ return undefined;
120
+ return Buffer.from(value, 'base64');
121
+ }
122
+ //# sourceMappingURL=passwords.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passwords.js","sourceRoot":"","sources":["../../src/admin/passwords.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Hono } from 'hono';
2
+ import type { GeekityEnv } from '../env.ts';
3
+ /** Where the editor posts a body it wants to see rendered. */
4
+ export declare const PREVIEW_PATH = "/admin/preview";
5
+ /**
6
+ * Render an unsaved body the way the public site would render it.
7
+ *
8
+ * The point of a preview is that it is not a second renderer. This builds the
9
+ * same {@link Document} shape the parser produces, runs the body through the
10
+ * same {@link renderMarkdown}, hands it to the same `documentContext`, and
11
+ * renders it through the theme's own post or page layout — so what the preview
12
+ * shows is what publishing would put on the site, theme overrides included.
13
+ *
14
+ * Nothing is written and nothing is indexed: the document exists for the
15
+ * length of one response.
16
+ */
17
+ export declare function mountPreview(app: Hono<GeekityEnv>): void;
18
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAM1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM5C,8DAA8D;AAC9D,eAAO,MAAM,YAAY,mBAA4B,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAYxD"}
@@ -0,0 +1,83 @@
1
+ import { renderMarkdown } from '../content/markdown.js';
2
+ import { defaultPermalink, slugify } from '../content/slug.js';
3
+ import { normalizeBody } from '../content/writer.js';
4
+ import { documentContext } from '../web/context.js';
5
+ import { TEMPLATES } from '../web/render.js';
6
+ import { splitTags } from './documents.js';
7
+ import { ADMIN_PREFIX } from './session.js';
8
+ /** Where the editor posts a body it wants to see rendered. */
9
+ export const PREVIEW_PATH = `${ADMIN_PREFIX}/preview`;
10
+ /**
11
+ * Render an unsaved body the way the public site would render it.
12
+ *
13
+ * The point of a preview is that it is not a second renderer. This builds the
14
+ * same {@link Document} shape the parser produces, runs the body through the
15
+ * same {@link renderMarkdown}, hands it to the same `documentContext`, and
16
+ * renders it through the theme's own post or page layout — so what the preview
17
+ * shows is what publishing would put on the site, theme overrides included.
18
+ *
19
+ * Nothing is written and nothing is indexed: the document exists for the
20
+ * length of one response.
21
+ */
22
+ export function mountPreview(app) {
23
+ app.post(PREVIEW_PATH, async (c) => {
24
+ const body = await c.req.parseBody();
25
+ const type = text(body['type']) === 'page' ? 'page' : 'post';
26
+ return c.html(c.var.renderer.render(type === 'post' ? TEMPLATES.post : TEMPLATES.page, documentContext(previewDocument(c, { type, body }))));
27
+ });
28
+ }
29
+ /** What a submitted editor form looks like as a document nobody has saved. */
30
+ function previewDocument(c, input) {
31
+ const { type, body } = input;
32
+ const title = text(body['title']).trim();
33
+ const date = type === 'post' ? orNow(text(body['date']).trim()) : undefined;
34
+ const slug = slugify(text(body['slug']).trim()) || slugify(title) || 'preview';
35
+ const markdown = normalizeBody(text(body['body']));
36
+ return {
37
+ type,
38
+ // A path and a permalink the preview needs to have, because the theme
39
+ // prints them; neither is ever written anywhere.
40
+ path: `${type === 'post' ? 'posts' : 'pages'}/${slug}.md`,
41
+ slug,
42
+ permalink: permalinkFor({ type, slug, date, submitted: text(body['permalink']).trim() }),
43
+ title: title === '' ? 'Untitled' : title,
44
+ ...(date === undefined ? {} : { date }),
45
+ tags: splitTags(text(body['tags'])),
46
+ draft: body['draft'] !== undefined,
47
+ ...(text(body['description']) === '' ? {} : { description: text(body['description']).trim() }),
48
+ ...(currentUsername(c) === undefined ? {} : { author: currentUsername(c) }),
49
+ extra: {},
50
+ body: markdown,
51
+ html: renderMarkdown(markdown),
52
+ // A preview is not a version of anything, so it has no hash to be
53
+ // mistaken for one.
54
+ hash: '',
55
+ };
56
+ }
57
+ /** The permalink to show: the one the form carried, or the one the slug implies. */
58
+ function permalinkFor(input) {
59
+ if (input.submitted !== '')
60
+ return input.submitted;
61
+ try {
62
+ return defaultPermalink({ type: input.type, slug: input.slug, date: input.date });
63
+ }
64
+ catch {
65
+ return '/';
66
+ }
67
+ }
68
+ /** A date field that is empty or unusable becomes now, so the layout has one. */
69
+ function orNow(value) {
70
+ return /^\d{4}-\d{2}-\d{2}/.test(value) ? value : new Date().toISOString();
71
+ }
72
+ /** The signed-in user's login, so a byline in the theme has something to print. */
73
+ function currentUsername(c) {
74
+ const userId = c.var.session?.userId;
75
+ if (userId == null)
76
+ return undefined;
77
+ return c.var.admin.getUserById(userId)?.username;
78
+ }
79
+ /** A form field as a string. A file upload, or a missing field, is the empty one. */
80
+ function text(value) {
81
+ return typeof value === 'string' ? value : '';
82
+ }
83
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { Hono, MiddlewareHandler } from 'hono';
2
+ import type { GeekityEnv } from '../env.ts';
3
+ /** Where the login form lives. */
4
+ export declare const LOGIN_PATH = "/admin/login";
5
+ /** Where the first-run form lives. */
6
+ export declare const SETUP_PATH = "/admin/setup";
7
+ /** Where the logout form posts. */
8
+ export declare const LOGOUT_PATH = "/admin/logout";
9
+ /** Where the dashboard's quick draft form posts. */
10
+ export declare const QUICK_DRAFT_PATH = "/admin/quick-draft";
11
+ /** One entry in the admin's left-hand navigation. */
12
+ export interface AdminSection {
13
+ /** The name a screen passes as `section` to mark itself current. */
14
+ section: string;
15
+ /** What the link says. */
16
+ label: string;
17
+ /** Where it goes. */
18
+ url: string;
19
+ }
20
+ /**
21
+ * The sections of the admin, in the order doc-5 lists them.
22
+ *
23
+ * Every screen renders the same list and marks one of them, so a screen added
24
+ * later only has to name its section. The screens behind most of these links
25
+ * are still placeholders; the navigation is the shape of the whole admin, not
26
+ * of the part that is built.
27
+ */
28
+ export declare const ADMIN_SECTIONS: readonly AdminSection[];
29
+ /** How many recent posts the dashboard lists. */
30
+ export declare const DASHBOARD_RECENT_POSTS = 5;
31
+ /** Where the editor for a post lives. */
32
+ export declare function postEditorPath(slug: string): string;
33
+ /**
34
+ * Register the admin on a Hono app.
35
+ *
36
+ * Everything under `/admin` goes through one guard, registered as middleware
37
+ * rather than per route, so a path with no handler of its own — `/admin/posts`
38
+ * before TASK-11 builds it — still redirects an anonymous visitor to the login
39
+ * form instead of falling through to the public site's 404.
40
+ *
41
+ * The guard decides three things, in order: whether the site has any users at
42
+ * all (none means every admin URL is the setup form), whether the request
43
+ * carries a live session, and, for anything but a GET, whether it carries this
44
+ * session's CSRF token.
45
+ */
46
+ export declare function mountAdmin(app: Hono<GeekityEnv>): void;
47
+ /**
48
+ * The one gate in front of every admin URL.
49
+ *
50
+ * It is exported so a site that mounts its own admin routes can put them
51
+ * behind the same guard, and so the ordering is testable.
52
+ */
53
+ export declare const guard: MiddlewareHandler<GeekityEnv>;
54
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/admin/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAM7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAsB5C,kCAAkC;AAClC,eAAO,MAAM,UAAU,iBAA0B,CAAC;AAClD,sCAAsC;AACtC,eAAO,MAAM,UAAU,iBAA0B,CAAC;AAClD,mCAAmC;AACnC,eAAO,MAAM,WAAW,kBAA2B,CAAC;AACpD,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,uBAAgC,CAAC;AAE9D,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAOjD,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CA2RtD;AAKD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,iBAAiB,CAAC,UAAU,CA+B/C,CAAC"}