@jant/core 0.6.16 → 0.7.1

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 (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -53,8 +53,23 @@
53
53
  {{- $sourceUrl := string ($post.Params.source_url | default "") -}}
54
54
  {{- $cite := "" -}}
55
55
  {{- if ne $sourceUrl "" -}}{{- $cite = printf ` cite=%q` $sourceUrl -}}{{- end -}}
56
- {{- $block := printf "<blockquote%s><p>%s</p></blockquote>" $cite (transform.XMLEscape $quoteText) -}}
57
- {{- $parts = $parts | append $block -}}
56
+ {{- /* Line breaks the author typed must survive as markup: the site
57
+ keeps them with `white-space: pre-line`, but feed readers strip
58
+ CSS. Blank lines split <p> blocks, single newlines become <br/>
59
+ (mirrors `renderPlainTextHtml` in `lib/feed.ts`). */ -}}
60
+ {{- $quoteBlocks := slice -}}
61
+ {{- range split (replaceRE `\r\n?` "\n" $quoteText) "\n\n" -}}
62
+ {{- $para := trim . " \t\n" -}}
63
+ {{- if ne $para "" -}}
64
+ {{- $lines := slice -}}
65
+ {{- range split $para "\n" -}}{{- $lines = $lines | append (transform.XMLEscape .) -}}{{- end -}}
66
+ {{- $quoteBlocks = $quoteBlocks | append (printf "<p>%s</p>" (delimit $lines "<br/>")) -}}
67
+ {{- end -}}
68
+ {{- end -}}
69
+ {{- if gt (len $quoteBlocks) 0 -}}
70
+ {{- $block := printf "<blockquote%s>%s</blockquote>" $cite (delimit $quoteBlocks "\n") -}}
71
+ {{- $parts = $parts | append $block -}}
72
+ {{- end -}}
58
73
  {{- /* Attribution line — prefer name, fall back to URL host or the
59
74
  raw URL itself (mirrors main site's extractDisplayDomain fallback
60
75
  chain in `lib/url.ts`). */ -}}
@@ -88,6 +103,94 @@
88
103
  {{- end -}}
89
104
  {{- end -}}
90
105
 
106
+ {{- /* --- Attachments --- */ -}}
107
+ {{- /* One container per post, marked `data-post-media` — the attribute the
108
+ site puts on its gallery strip, so a consumer styles the strip instead of
109
+ reassembling it from the Media RSS elements. In a thread this is also the
110
+ only surface that says which post an attachment belongs to. Mirrors
111
+ `renderMediaItem` in `packages/core/src/lib/feed.ts`. */ -}}
112
+ {{- $mediaList := $post.Params.media | default slice -}}
113
+ {{- if gt (len $mediaList) 0 -}}
114
+ {{- $items := slice -}}
115
+ {{- range $mediaList -}}
116
+ {{- $kind := string (.kind | default "document") -}}
117
+ {{- $mime := string (.mime_type | default "application/octet-stream") -}}
118
+ {{- /* `trim` wraps the index rather than piping into it: a Go template
119
+ pipe binds to the last argument, so `index … 0 | trim " "` would trim
120
+ the index instead of the result. */ -}}
121
+ {{- $mimeLabel := trim (index (split $mime ";") 0) " " -}}
122
+ {{- $src := (string .src) | absURL -}}
123
+ {{- $name := string (.original_name | default "") -}}
124
+ {{- $alt := string (.alt | default "") -}}
125
+ {{- /* Duration and size, in the shapes `formatFeedDuration` and
126
+ `formatFeedBytes` produce. */ -}}
127
+ {{- $metaParts := slice -}}
128
+ {{- with .duration_seconds -}}
129
+ {{- $total := int (math.Round .) -}}
130
+ {{- if gt $total 0 -}}
131
+ {{- $metaParts = $metaParts | append (printf "%d:%02d" (div $total 60) (mod $total 60)) -}}
132
+ {{- end -}}
133
+ {{- end -}}
134
+ {{- with .size -}}
135
+ {{- $b := int . -}}
136
+ {{- if gt $b 0 -}}
137
+ {{- if lt $b 1024 -}}
138
+ {{- $metaParts = $metaParts | append (printf "%d B" $b) -}}
139
+ {{- else if lt $b 1048576 -}}
140
+ {{- $metaParts = $metaParts | append (printf "%d KB" (int (math.Round (div (float $b) 1024.0)))) -}}
141
+ {{- else -}}
142
+ {{- $metaParts = $metaParts | append (printf "%.1f MB" (div (float $b) 1048576.0)) -}}
143
+ {{- end -}}
144
+ {{- end -}}
145
+ {{- end -}}
146
+ {{- $meta := delimit $metaParts " · " -}}
147
+ {{- $metaSuffix := "" -}}
148
+ {{- if ne $meta "" -}}{{- $metaSuffix = printf " (%s)" (transform.XMLEscape $meta) -}}{{- end -}}
149
+ {{- $dims := "" -}}
150
+ {{- if and .width .height -}}{{- $dims = printf ` width="%d" height="%d"` (int .width) (int .height) -}}{{- end -}}
151
+
152
+ {{- if eq $kind "image" -}}
153
+ {{- $caption := "" -}}
154
+ {{- if ne $alt "" -}}{{- $caption = printf "<figcaption>%s</figcaption>" (transform.XMLEscape $alt) -}}{{- end -}}
155
+ {{- /* `%q` is Go string quoting, not attribute escaping: a `"` or `\`
156
+ in the alt would leave the attribute malformed. URLs go through
157
+ `%q` because they never carry either; author text goes through
158
+ `transform.XMLEscape`, the same as the caption beside it. */ -}}
159
+ {{- $items = $items | append (printf `<figure><a href=%q><img src=%q alt="%s"%s/></a>%s</figure>` $src $src (transform.XMLEscape $alt) $dims $caption) -}}
160
+ {{- else if eq $kind "video" -}}
161
+ {{- /* A poster is written only when there is a real still; the pipeline
162
+ leaves nothing usable otherwise, and `<img src="clip.mp4">` was the
163
+ bug that taught us. The link sits outside the <video> so it survives
164
+ a sanitizer that drops the player with its children, and
165
+ `preload="none"` keeps a reader from pulling the whole file. */ -}}
166
+ {{- $posterAttr := "" -}}
167
+ {{- with .poster -}}{{- $posterAttr = printf ` poster=%q` ((string .) | absURL) -}}{{- end -}}
168
+ {{- $altSuffix := "" -}}
169
+ {{- if ne $alt "" -}}{{- $altSuffix = printf ": %s" (transform.XMLEscape $alt) -}}{{- end -}}
170
+ {{- $items = $items | append (printf `<figure><video controls preload="none"%s%s><source src=%q type=%q/></video><figcaption><a href=%q>▶ Watch video</a>%s%s</figcaption></figure>` $posterAttr $dims $src $mimeLabel $src $metaSuffix $altSuffix) -}}
171
+ {{- else if eq $kind "text" -}}
172
+ {{- /* Markdown a browser would download or dump unstyled, so the link
173
+ goes to the page that renders it. */ -}}
174
+ {{- $href := printf "%s/text/%s" (strings.TrimSuffix "/" $permalink) (string .id) -}}
175
+ {{- $label := printf "📎 [%s] %s" (transform.XMLEscape $mimeLabel) (transform.XMLEscape (or $name "Attached text")) -}}
176
+ {{- $textMeta := $metaSuffix -}}
177
+ {{- with .chars -}}
178
+ {{- if gt (int .) 0 -}}{{- $textMeta = printf " (%d chars)" (int .) -}}{{- end -}}
179
+ {{- end -}}
180
+ {{- $summarySuffix := "" -}}
181
+ {{- with .summary -}}
182
+ {{- if ne (string .) "" -}}{{- $summarySuffix = printf ": %s" (transform.XMLEscape (string .)) -}}{{- end -}}
183
+ {{- end -}}
184
+ {{- $items = $items | append (printf `<p><a href=%q>%s</a>%s%s</p>` $href $label $textMeta $summarySuffix) -}}
185
+ {{- else -}}
186
+ {{- $fallbackName := cond (eq $kind "audio") "Audio" "Attachment" -}}
187
+ {{- $label := printf "📎 [%s] %s" (transform.XMLEscape $mimeLabel) (transform.XMLEscape (or $name $fallbackName)) -}}
188
+ {{- $items = $items | append (printf `<p><a href=%q>%s</a>%s</p>` $src $label $metaSuffix) -}}
189
+ {{- end -}}
190
+ {{- end -}}
191
+ {{- $parts = $parts | append (printf "<div data-post-media>\n%s\n</div>" (delimit $items "\n")) -}}
192
+ {{- end -}}
193
+
91
194
  {{- /* --- Star rating --- */ -}}
92
195
  {{- with $post.Params.rating -}}
93
196
  {{- $r := int . -}}
@@ -1,4 +1,4 @@
1
- {{- $jant := hugo.Data.jant -}}
1
+ {{- $jant := partial "jant-data.html" . -}}
2
2
  {{- $footerHtml := "" -}}
3
3
  {{- with $jant -}}{{- with .site_footer_html -}}{{- $footerHtml = . -}}{{- end -}}{{- end -}}
4
4
  {{- $showBranding := false -}}
@@ -1,4 +1,4 @@
1
- {{- $jant := hugo.Data.jant -}}
1
+ {{- $jant := partial "jant-data.html" . -}}
2
2
  {{- $siteTitle := .Site.Title -}}
3
3
  {{- $pageTitle := "" -}}
4
4
  {{- if .IsHome -}}
@@ -1,14 +1,16 @@
1
- {{- $jant := hugo.Data.jant -}}
1
+ {{- $jant := partial "jant-data.html" . -}}
2
2
  {{- $showAvatar := false -}}
3
3
  {{- with $jant -}}{{- if eq .show_header_avatar true -}}{{- $showAvatar = true -}}{{- end -}}{{- end -}}
4
4
  {{- $avatarUrl := "" -}}
5
5
  {{- with $jant -}}{{- with .site_avatar_url -}}{{- $avatarUrl = . -}}{{- end -}}{{- end -}}
6
6
 
7
- {{- /* Split nav items by placement: "header" (inline) vs "more" (supplemental dropdown). */ -}}
7
+ {{- /* Split nav items by placement: "header" (inline) vs "more" (supplemental dropdown).
8
+ `settings` is exported for an import back into Jant; the static site has no page for it. */ -}}
8
9
  {{- $headerItems := slice -}}
9
10
  {{- $moreItems := slice -}}
10
11
  {{- with $jant -}}{{- range .nav -}}
11
- {{- if eq .placement "more" -}}
12
+ {{- if eq .system_key "settings" -}}
13
+ {{- else if eq .placement "more" -}}
12
14
  {{- $moreItems = $moreItems | append . -}}
13
15
  {{- else -}}
14
16
  {{- $headerItems = $headerItems | append . -}}
@@ -0,0 +1,23 @@
1
+ {{- /*
2
+ Returns the `jant` table from `data/jant.toml` — nav, branding, and the
3
+ collections directory.
4
+
5
+ Hugo moved site data to `hugo.Data` in v0.160.0 and deprecated `site.Data`
6
+ in v0.156.0, so neither accessor works everywhere: `hugo.Data` is a render
7
+ error before it existed, and `site.Data` is a deprecation warning today and
8
+ a removal tomorrow. Only the taken branch of an `if` is evaluated, so
9
+ branching on the running version reads whichever one that version has.
10
+ `hugo.Version` compares semantically, not as a string.
11
+
12
+ Hugo rewrites a single `return` per template, hence the assignment rather
13
+ than a `return` in each branch. Every template reads the data through this
14
+ partial, so the day the old branch stops being worth carrying there is one
15
+ place to change.
16
+ */ -}}
17
+ {{- $data := dict -}}
18
+ {{- if ge hugo.Version "0.160.0" -}}
19
+ {{- $data = hugo.Data -}}
20
+ {{- else -}}
21
+ {{- $data = site.Data -}}
22
+ {{- end -}}
23
+ {{- return (index $data "jant") -}}
@@ -0,0 +1,48 @@
1
+ {{- /*
2
+ The Thread roots on Latest, in the order Jant lists them: the home page
3
+ (`assembleTimeline`, `posts.list`) or `/latest/feed`
4
+ (`latestFeedSelection` with `ignorePinnedSort`).
5
+
6
+ Takes a dict: `feed`, true for the Atom feed.
7
+
8
+ A root is on Latest when it is published and public: Hidden from Latest
9
+ and private roots are left out, as are drafts, which are exported as Hugo
10
+ drafts and never reach here; status and visibility are checked again so a
11
+ `--buildDrafts` preview agrees with Jant.
12
+
13
+ Order, each key after the one before it:
14
+ pinned page only: pinned roots first, most recently pinned first
15
+ activity Thread activity (`last_activity_at`, else `date`), newest
16
+ first, so a Thread that gains a reply comes back up; a quiet
17
+ reply doesn't move it, because Jant doesn't count it
18
+ ID newest first, so no two roots tie
19
+
20
+ `src/__tests__/export-feed-order.test.ts` builds a site with Hugo and
21
+ compares the page and the feed with Jant's.
22
+ */ -}}
23
+ {{- $feed := .feed -}}
24
+ {{- $roots := where (where site.Pages "Type" "post") "Kind" "section" -}}
25
+ {{- $keyed := slice -}}
26
+ {{- range $roots -}}
27
+ {{- $params := .Params -}}
28
+ {{- if and (eq ($params.status | default "published") "published") (eq $params.visibility "public") -}}
29
+ {{- $pinned := 0 -}}
30
+ {{- if not $feed -}}
31
+ {{- with $params.pinned_at -}}{{- $pinned = (time.AsTime .).Unix -}}{{- end -}}
32
+ {{- end -}}
33
+ {{- $activity := .Date.Unix -}}
34
+ {{- with $params.last_activity_at -}}{{- $activity = (time.AsTime .).Unix -}}{{- end -}}
35
+ {{- $keyed = $keyed | append (dict
36
+ "key" (printf "%020d %020d %s" $pinned $activity $params.id)
37
+ "post" .
38
+ ) -}}
39
+ {{- end -}}
40
+ {{- end -}}
41
+
42
+ {{- $ordered := slice -}}
43
+ {{- if $keyed -}}
44
+ {{- range sort $keyed "key" "desc" -}}
45
+ {{- $ordered = $ordered | append .post -}}
46
+ {{- end -}}
47
+ {{- end -}}
48
+ {{- return $ordered -}}
@@ -8,10 +8,14 @@
8
8
  `<jant-media-lightbox>` delegates clicks on those anchors.
9
9
 
10
10
  Layout: mirrors MediaGallery.tsx — one horizontal row with a computed
11
- `rowHeight` (driven by the first visual's aspect ratio) so mixed sizes
12
- align into a clean strip. Single-visual posts expand to their natural
13
- aspect ratio (capped at 24rem / 85% width). Non-visual attachments
14
- (audio/doc/text) use a 3:4 portrait card sized to the row height.
11
+ `rowHeight` so mixed sizes align into a clean strip. The opening visual
12
+ leads (320px / its ratio), the widest visual is held to 520px so a landscape
13
+ video never swallows the column, and the result is clamped to 240–384px —
14
+ 384 being the same 24rem cap a lone visual gets, so a second attachment
15
+ never makes the first picture grow. Non-visual attachments (audio/doc/text)
16
+ do not enter that math; their 3:4 portrait cards are sized from its result.
17
+ Single-visual posts skip the strip and expand to their natural aspect ratio
18
+ (capped at 24rem / 85% width).
15
19
 
16
20
  Inline styles (not Tailwind utilities) because the exported theme ships
17
21
  no utility CSS — only tokens.css + main.css + theme.css + site-media.css.
@@ -27,8 +31,7 @@
27
31
  {{- $items := slice -}}
28
32
  {{- $lightbox := slice -}}
29
33
  {{- $lbIdx := 0 -}}
30
- {{- $hasNonVisual := false -}}
31
- {{- $firstRatio := 0.0 -}}
34
+ {{- $visualRatios := slice -}}
32
35
  {{- range $media -}}
33
36
  {{- $kind := .kind | default "file" -}}
34
37
  {{- $item := . -}}
@@ -46,13 +49,15 @@
46
49
  {{- $lightbox = $lightbox | append $lbEntry -}}
47
50
  {{- $items = $items | append (merge $item (dict "_kind" $kind "_lbIdx" $lbIdx)) -}}
48
51
  {{- $lbIdx = add $lbIdx 1 -}}
49
- {{- /* Capture first visual's aspect ratio */ -}}
50
- {{- if and (eq $firstRatio 0.0) $item.width $item.height (gt (float $item.width) 0.0) (gt (float $item.height) 0.0) -}}
51
- {{- $firstRatio = div (float $item.width) (float $item.height) -}}
52
+ {{- /* Every visual's aspect ratio, in display order — 4:3 when unknown,
53
+ matching getMediaAspectRatio() in MediaGallery.tsx. */ -}}
54
+ {{- $ratio := 1.3333 -}}
55
+ {{- if and $item.width $item.height (gt (float $item.width) 0.0) (gt (float $item.height) 0.0) -}}
56
+ {{- $ratio = div (float $item.width) (float $item.height) -}}
52
57
  {{- end -}}
58
+ {{- $visualRatios = $visualRatios | append $ratio -}}
53
59
  {{- else -}}
54
60
  {{- $items = $items | append (merge $item (dict "_kind" $kind)) -}}
55
- {{- $hasNonVisual = true -}}
56
61
  {{- end -}}
57
62
  {{- end -}}
58
63
 
@@ -62,14 +67,19 @@
62
67
  {{- $firstKind := $first._kind -}}
63
68
  {{- $singleVisual := and $single (or (eq $firstKind "image") (eq $firstKind "video")) -}}
64
69
 
65
- {{- /* rowHeight — mirrors MediaGallery.tsx. 4/3 fallback when no visuals. */ -}}
66
- {{- $rowMin := cond $hasNonVisual 220 280 -}}
67
- {{- $rowMax := cond $hasNonVisual 300 440 -}}
68
- {{- $rowHeight := cond $hasNonVisual 220 360 -}}
69
- {{- if and (not $singleVisual) (gt $count 1) (gt $firstRatio 0.0) -}}
70
- {{- $ratio := cond (lt $firstRatio 0.5) 0.5 $firstRatio -}}
71
- {{- $computed := math.Round (div 320.0 $ratio) -}}
72
- {{- $rowHeight = cond (gt $computed $rowMax) $rowMax (cond (lt $computed $rowMin) $rowMin (int $computed)) -}}
70
+ {{- /* rowHeight — mirrors getGalleryRowHeight() in MediaGallery.tsx. */ -}}
71
+ {{- $rowMin := 240 -}}
72
+ {{- $rowMax := 384 -}}
73
+ {{- $rowHeight := $rowMin -}}
74
+ {{- if $visualRatios -}}
75
+ {{- $widest := 0.0 -}}
76
+ {{- range $visualRatios -}}
77
+ {{- if gt . $widest }}{{ $widest = . }}{{ end -}}
78
+ {{- end -}}
79
+ {{- $byLead := div 320.0 (index $visualRatios 0) -}}
80
+ {{- $byWidestItem := div 520.0 $widest -}}
81
+ {{- $computed := math.Round (cond (lt $byWidestItem $byLead) $byWidestItem $byLead) -}}
82
+ {{- $rowHeight = cond (gt $computed (float $rowMax)) $rowMax (cond (lt $computed (float $rowMin)) $rowMin (int $computed)) -}}
73
83
  {{- end -}}
74
84
  {{- $docCardWidth := int (math.Round (mul (float $rowHeight) 0.75)) -}}
75
85
 
@@ -80,7 +90,7 @@
80
90
  <div data-post-media
81
91
  {{- if $lightbox }} data-lightbox-group="{{ $lightbox | jsonify }}"{{- end -}}
82
92
  {{- if not $singleVisual }} tabindex="0" role="group" aria-label="Media gallery"{{- end -}}
83
- style="display: flex; gap: 0.5rem;
93
+ style="display: flex; gap: 0.75rem;
84
94
  {{- if not $singleVisual }} overflow-x: auto; scroll-behavior: smooth; scrollbar-width: none; -ms-overflow-style: none;{{- end }}">
85
95
  {{- range $items -}}
86
96
  {{- $k := ._kind -}}
@@ -130,10 +140,14 @@
130
140
  {{- else -}}
131
141
  {{- $videoWrapStyle = printf "%s display: block; flex-shrink: 0; width: %dpx; max-width: 85%%;" $videoWrapStyle $itemWidth -}}
132
142
  {{- end -}}
143
+ {{- /* Fit travels with the size, as in MediaGallery.tsx, and
144
+ site-media.css sets none: a clip in a row is cropped to the
145
+ row's height like the pictures beside it; a lone clip is
146
+ letterboxed in case its frames disagree with the stored size. */ -}}
133
147
  {{- $videoStyle := cond $singleVisual
134
148
  (cond (and (gt (int (default 0 .width)) 0) (gt (int (default 0 .height)) 0))
135
- (printf "display: block; width: 100%%; height: auto; aspect-ratio: %d/%d;" (int .width) (int .height))
136
- "display: block; width: 100%; height: auto;")
149
+ (printf "display: block; width: 100%%; height: auto; aspect-ratio: %d/%d; object-fit: contain;" (int .width) (int .height))
150
+ "display: block; width: 100%; height: auto; object-fit: contain;")
137
151
  (printf "display: block; width: 100%%; height: %dpx; object-fit: cover;" $rowHeight) -}}
138
152
  {{- if $isShort -}}
139
153
  <div class="media-video-wrap media-video-wrap-short" style="{{ $videoWrapStyle | safeCSS }}">
@@ -2,26 +2,27 @@
2
2
  {{- $total := $paginator.TotalPages -}}
3
3
  {{- $current := $paginator.PageNumber -}}
4
4
  {{- if gt $total 1 -}}
5
- {{- /* Mirror main site's getPageNumbers: always include 1, last, current,
6
- and current±1. Insert 0 as an ellipsis marker for gaps. */ -}}
5
+ {{- /* Mirror main site's getPageNumbers: past seven pages, always seven
6
+ slots — 1, last, and a three-page window around the current page.
7
+ The second and second-to-last slots hold a page or 0 (an ellipsis
8
+ marker), so an ellipsis never stands in for a single page. */ -}}
7
9
  {{- $pages := slice -}}
8
10
  {{- if le $total 7 -}}
9
11
  {{- range seq 1 $total -}}
10
12
  {{- $pages = $pages | append . -}}
11
13
  {{- end -}}
12
14
  {{- else -}}
13
- {{- $set := slice 1 $total $current -}}
14
- {{- if gt $current 1 -}}{{- $set = $set | append (sub $current 1) -}}{{- end -}}
15
- {{- if lt $current $total -}}{{- $set = $set | append (add $current 1) -}}{{- end -}}
16
- {{- $sorted := sort (uniq $set) -}}
17
- {{- $prev := 0 -}}
18
- {{- range $i, $p := $sorted -}}
19
- {{- if and (gt $i 0) (gt (sub $p $prev) 1) -}}
20
- {{- $pages = $pages | append 0 -}}
21
- {{- end -}}
22
- {{- $pages = $pages | append $p -}}
23
- {{- $prev = $p -}}
15
+ {{- $start := sub $current 1 -}}
16
+ {{- if lt $start 3 -}}{{- $start = 3 -}}{{- end -}}
17
+ {{- if gt $start (sub $total 4) -}}{{- $start = sub $total 4 -}}{{- end -}}
18
+ {{- $end := add $start 2 -}}
19
+ {{- $pages = $pages | append 1 -}}
20
+ {{- $pages = $pages | append (cond (eq $start 3) 2 0) -}}
21
+ {{- range seq $start $end -}}
22
+ {{- $pages = $pages | append . -}}
24
23
  {{- end -}}
24
+ {{- $pages = $pages | append (cond (eq $end (sub $total 2)) (sub $total 1) 0) -}}
25
+ {{- $pages = $pages | append $total -}}
25
26
  {{- end -}}
26
27
  <nav class="pagination" aria-label="Pagination">
27
28
  {{- if $paginator.HasPrev -}}
@@ -5,6 +5,10 @@
5
5
  {{- $collections := $post.Params.collections | default slice -}}
6
6
  {{- $slug := $post.Params.slug | default $post.Slug -}}
7
7
  {{- $linkUrl := $post.Params.link_url -}}
8
+ {{- /* A reply is not rendered as a page (`build: render: never`), so its
9
+ `.RelPermalink` is empty. The exporter registers every reply slug as an
10
+ alias on the root, and that alias is the address a reply has. */ -}}
11
+ {{- $permalink := $post.RelPermalink | default (printf "/%s/" $slug | relURL) -}}
8
12
  <article class="post-card post-card-{{ $format }}" data-format="{{ $format }}" data-slug="{{ $slug }}"
9
13
  {{- if $post.Params.pinned_at }} data-post-pinned{{ end -}}
10
14
  {{- if $post.Params.featured_at }} data-post-featured{{ end -}}
@@ -27,7 +31,7 @@
27
31
  <a href="{{ $linkUrl }}" rel="noopener noreferrer" target="_blank">{{ $post.Title }}</a>
28
32
  </h2>
29
33
  {{- else if $post.Title -}}
30
- <h2 class="post-card-title post-card-link-title"><a href="{{ $post.RelPermalink }}">{{ $post.Title }}</a></h2>
34
+ <h2 class="post-card-title post-card-link-title"><a href="{{ $permalink }}">{{ $post.Title }}</a></h2>
31
35
  {{- end -}}
32
36
  {{- /* Prefer Hugo's rendered HTML body — preserves paragraphs, links,
33
37
  emphasis, lists. Falls back to the plain-text `summary_text` stored
@@ -73,7 +77,7 @@
73
77
  {{- end -}}
74
78
  {{- else -}}
75
79
  {{- with $post.Title -}}
76
- <h2 class="post-card-title"><a href="{{ $post.RelPermalink }}">{{ . }}</a></h2>
80
+ <h2 class="post-card-title"><a href="{{ $permalink }}">{{ . }}</a></h2>
77
81
  {{- end -}}
78
82
  {{- /* Prefer Hugo's rendered HTML summary — preserves paragraphs, links,
79
83
  emphasis, lists. Falls back to the plain-text `summary_text` stored
@@ -85,7 +89,7 @@
85
89
  {{- end -}}
86
90
  {{- end -}}
87
91
 
88
- {{ partial "media-gallery.html" (dict "media" $media "permalink" $post.RelPermalink) }}
92
+ {{ partial "media-gallery.html" (dict "media" $media "permalink" $permalink) }}
89
93
 
90
94
  <footer class="post-card-footer post-menu-footer" data-post-meta>
91
95
  <div class="post-footer-meta">
@@ -96,7 +100,7 @@
96
100
  </svg>
97
101
  </span>
98
102
  {{- end -}}
99
- <a class="post-footer-link post-card-permalink" href="{{ $post.RelPermalink }}">
103
+ <a class="post-footer-link post-card-permalink" href="{{ $permalink }}">
100
104
  <time datetime="{{ $post.Date.Format "2006-01-02T15:04:05Z07:00" }}">
101
105
  {{ $post.Date.Format "Jan 2, 2006" }}
102
106
  </time>
@@ -69,7 +69,8 @@
69
69
  </div>
70
70
  {{- end -}}
71
71
 
72
- {{ partial "media-gallery.html" (dict "media" $media "permalink" $reply.RelPermalink) }}
72
+ {{- /* Replies are not rendered as pages, so the address is the reply's alias on the root. */ -}}
73
+ {{ partial "media-gallery.html" (dict "media" $media "permalink" ($reply.RelPermalink | default (printf "/%s/" $slug | relURL))) }}
73
74
 
74
75
  <footer class="reply-footer post-menu-footer" data-post-meta>
75
76
  <div class="post-footer-meta">
@@ -0,0 +1,129 @@
1
+ {{- /*
2
+ The Thread roots a smart collection's conditions select, in its order.
3
+
4
+ The same set and order Jant shows a signed-out reader on the smart
5
+ collection's page (`smartCollections.toPostFilters` handed to
6
+ `posts.list`), computed from the exported front matter so the page keeps
7
+ up with the posts in this repository. Every condition reads the root:
8
+
9
+ format `format`
10
+ title `title`; for a quote, `source_name`, which is what Jant
11
+ stores in the title column
12
+ year `date`, the publication time, in UTC
13
+ media the root's own `media` entries: `any`, `none`, or a list of
14
+ kinds, any one of which matches
15
+ replies whether the root has published replies
16
+ visibility `public` and `latest_hidden` match `visibility`; `featured`
17
+ matches a root with `featured_at`
18
+ collection a `collections` entry with that slug
19
+
20
+ Drafts and private roots are exported as Hugo drafts and never reach here;
21
+ they are checked again so a `--buildDrafts` preview agrees with Jant.
22
+
23
+ Takes a dict: `page`, the smart collection's section page, and `feed`,
24
+ true for its Atom feed.
25
+
26
+ Orders, each ending on the post ID so no two roots tie:
27
+ newest Thread activity (`last_activity_at`, else `date`), newest first
28
+ oldest publication, oldest first
29
+ rating_desc rated before unrated, higher rating first, then as newest.
30
+ On the page, fewer than two rated roots fall back to
31
+ newest, as Jant's page does; the feed keeps the stored
32
+ order, as Jant's feed does.
33
+
34
+ `src/__tests__/export-smart-collection.test.ts` builds a site with Hugo
35
+ and compares every condition and order against Jant's own list.
36
+ */ -}}
37
+ {{- $page := .page -}}
38
+ {{- $feed := .feed -}}
39
+ {{- $selection := $page.Params.selection | default dict -}}
40
+ {{- $roots := where (where site.Pages "Type" "post") "Kind" "section" -}}
41
+ {{- $members := slice -}}
42
+ {{- range $roots -}}
43
+ {{- $post := . -}}
44
+ {{- $params := .Params -}}
45
+ {{- $matches := and (eq ($params.status | default "published") "published") (ne $params.visibility "private") -}}
46
+ {{- if and $matches (isset $selection "format") -}}
47
+ {{- $matches = eq $params.format $selection.format -}}
48
+ {{- end -}}
49
+ {{- if and $matches (isset $selection "title") -}}
50
+ {{- $title := $params.title -}}
51
+ {{- if eq $params.format "quote" -}}{{- $title = $params.source_name -}}{{- end -}}
52
+ {{- $matches = eq (gt (len (string ($title | default ""))) 0) $selection.title -}}
53
+ {{- end -}}
54
+ {{- if and $matches (isset $selection "year") -}}
55
+ {{- $matches = eq $post.Date.UTC.Year (int $selection.year) -}}
56
+ {{- end -}}
57
+ {{- if and $matches (isset $selection "media") -}}
58
+ {{- $media := $params.media | default slice -}}
59
+ {{- $wanted := $selection.media -}}
60
+ {{- if reflect.IsSlice $wanted -}}
61
+ {{- $found := false -}}
62
+ {{- range $media -}}{{- if in $wanted .kind -}}{{- $found = true -}}{{- end -}}{{- end -}}
63
+ {{- $matches = $found -}}
64
+ {{- else if eq $wanted "any" -}}
65
+ {{- $matches = gt (len $media) 0 -}}
66
+ {{- else -}}
67
+ {{- $matches = eq (len $media) 0 -}}
68
+ {{- end -}}
69
+ {{- end -}}
70
+ {{- if and $matches (isset $selection "replies") -}}
71
+ {{- $matches = eq (gt (len $post.Pages) 0) $selection.replies -}}
72
+ {{- end -}}
73
+ {{- if and $matches (isset $selection "visibility") -}}
74
+ {{- if eq $selection.visibility "featured" -}}
75
+ {{- $matches = ne ($params.featured_at | default "") "" -}}
76
+ {{- else -}}
77
+ {{- $matches = eq $params.visibility $selection.visibility -}}
78
+ {{- end -}}
79
+ {{- end -}}
80
+ {{- if and $matches (isset $selection "collection") -}}
81
+ {{- $inCollection := false -}}
82
+ {{- range $params.collections -}}
83
+ {{- if eq .slug $selection.collection -}}{{- $inCollection = true -}}{{- end -}}
84
+ {{- end -}}
85
+ {{- $matches = $inCollection -}}
86
+ {{- end -}}
87
+
88
+ {{- if $matches -}}
89
+ {{- $activity := $post.Date.Unix -}}
90
+ {{- with $params.last_activity_at -}}{{- $activity = (time.AsTime .).Unix -}}{{- end -}}
91
+ {{- $rated := ne $params.rating nil -}}
92
+ {{- $rating := 0 -}}
93
+ {{- if $rated -}}{{- $rating = int $params.rating -}}{{- end -}}
94
+ {{- $members = $members | append (dict
95
+ "post" $post
96
+ "id" $params.id
97
+ "published" $post.Date.Unix
98
+ "activity" $activity
99
+ "rated" $rated
100
+ "rating" $rating
101
+ ) -}}
102
+ {{- end -}}
103
+ {{- end -}}
104
+
105
+ {{- $order := $page.Params.sort_order | default "newest" -}}
106
+ {{- if and (not $feed) (eq $order "rating_desc") (le (len (where $members "rated" true)) 1) -}}
107
+ {{- $order = "newest" -}}
108
+ {{- end -}}
109
+
110
+ {{- /* One string key per root, fixed-width numbers first, so a single sort
111
+ orders by every key in turn. */ -}}
112
+ {{- $keyed := slice -}}
113
+ {{- range $members -}}
114
+ {{- $key := printf "%020d %s" .activity .id -}}
115
+ {{- if eq $order "oldest" -}}
116
+ {{- $key = printf "%020d %s" .published .id -}}
117
+ {{- else if eq $order "rating_desc" -}}
118
+ {{- $key = printf "%d %02d %020d %s" (cond .rated 1 0) .rating .activity .id -}}
119
+ {{- end -}}
120
+ {{- $keyed = $keyed | append (dict "key" $key "post" .post) -}}
121
+ {{- end -}}
122
+
123
+ {{- $ordered := slice -}}
124
+ {{- if $keyed -}}
125
+ {{- range sort $keyed "key" (cond (eq $order "oldest") "asc" "desc") -}}
126
+ {{- $ordered = $ordered | append .post -}}
127
+ {{- end -}}
128
+ {{- end -}}
129
+ {{- return $ordered -}}