@jant/core 0.3.39 → 1.0.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 (1162) hide show
  1. package/README.md +21 -33
  2. package/bin/commands/assets/upload.js +247 -0
  3. package/bin/commands/collections.js +268 -0
  4. package/bin/commands/import-site.js +1169 -343
  5. package/bin/commands/media.js +302 -0
  6. package/bin/commands/migrate-text-attachments.js +183 -0
  7. package/bin/commands/migrate.js +263 -2
  8. package/bin/commands/posts.js +262 -0
  9. package/bin/commands/search.js +53 -0
  10. package/bin/commands/settings.js +93 -0
  11. package/bin/commands/site/export.js +34 -33
  12. package/bin/commands/site/{localize-media.js → pull-media.js} +17 -17
  13. package/bin/lib/d1-query.js +15 -3
  14. package/bin/lib/http-api.js +223 -0
  15. package/bin/lib/media-upload.js +206 -0
  16. package/bin/lib/migration-runner.js +64 -0
  17. package/bin/lib/runtime-target.js +7 -2
  18. package/bin/lib/site-media-parser.js +71 -16
  19. package/bin/lib/site-pull-media.js +698 -0
  20. package/bin/lib/site-snapshot.js +0 -1
  21. package/dist/app-B5db9mS0.js +5 -0
  22. package/dist/{app-BfoG98VD.js → app-CwCepTko.js} +23938 -23014
  23. package/dist/client/.vite/manifest.json +3485 -0
  24. package/dist/client/_assets/chunks/02611a045a7fe83a12014e3debc9f731-BqEDtwXC.woff2 +0 -0
  25. package/dist/client/_assets/chunks/033466ef683afe931f7f520cfb42d928-BluLHEh9.woff2 +0 -0
  26. package/dist/client/_assets/chunks/04b8bde59cff68eeee74fb1914eb09c7-D5j2CLTt.woff2 +0 -0
  27. package/dist/client/_assets/chunks/057a6a98bda7fe57105ddaa99ec82015-DY_AoijT.woff2 +0 -0
  28. package/dist/client/_assets/chunks/05ac821472e235943ed1435e4bb8ecad-CUm8GiH6.woff2 +0 -0
  29. package/dist/client/_assets/chunks/05da12edb9d52210581dc6ec4541031f-BXAOx2XP.woff2 +0 -0
  30. package/dist/client/_assets/chunks/074c1f8483d5a4d8c45c8c5f3e4cbb32-CDqdMQm8.woff2 +0 -0
  31. package/dist/client/_assets/chunks/0968e4861204b51f62a2f8e9f15dd5e0-D5tLgF_8.woff2 +0 -0
  32. package/dist/client/_assets/chunks/0acd1fe2b2ea1ad1bfee7ae1fa139d27-BKK1m412.woff2 +0 -0
  33. package/dist/client/_assets/chunks/0c2ab6b295e55f356f8020d4e7747522-CkCQjQw3.woff2 +0 -0
  34. package/dist/client/_assets/chunks/0c5f9492af03a4fa42c784de94649de1-CQlJqksa.woff2 +0 -0
  35. package/dist/client/_assets/chunks/0d5dec931dc885f07fe5cd5af8bed675-C74x-U_U.woff2 +0 -0
  36. package/dist/client/_assets/chunks/0d9a936885a4c39077438effd3779cbd-Bw0fYF4S.woff2 +0 -0
  37. package/dist/client/_assets/chunks/0e645da524f7cfc0e8c3c03fb2b08428-BVeI_5Th.woff2 +0 -0
  38. package/dist/client/_assets/chunks/0f5e1a18987dbc84ca05188c129e1936-B6_lZ6fE.woff2 +0 -0
  39. package/dist/client/_assets/chunks/1076f0f6f66d28d7a2f16427faad4413-ubiXnD1P.woff2 +0 -0
  40. package/dist/client/_assets/chunks/112743a4ab5fdd1498dfdf2b11336380-B5FzQxTK.woff2 +0 -0
  41. package/dist/client/_assets/chunks/1211f03d3ff5759a702631445793f60e-DDcSiyCt.woff2 +0 -0
  42. package/dist/client/_assets/chunks/12ef1ba76bd20b004b865266a1aa70b3-BwmaD87S.woff2 +0 -0
  43. package/dist/client/_assets/chunks/135e83b403475c5dc9e49b43501a5b84-C5mE_KZS.woff2 +0 -0
  44. package/dist/client/_assets/chunks/1380210a722ac9aeef54005ad7b015c9-CjA-MP82.woff2 +0 -0
  45. package/dist/client/_assets/chunks/13b2c53b30e6a3e4de7132dbc18dbdfc-B9WOB7kv.woff2 +0 -0
  46. package/dist/client/_assets/chunks/147b24c2f08d03bbed30887d4cb3311c-IeeFCJ13.woff2 +0 -0
  47. package/dist/client/_assets/chunks/152395634a207579552f8cb54db88599-C2y2JyNZ.woff2 +0 -0
  48. package/dist/client/_assets/chunks/15d95680dc31cc6ce20e0a5464106dc4-CpD8dqRm.woff2 +0 -0
  49. package/dist/client/_assets/chunks/16663e567f81f4725a1522f37e18f71f-BNoxS9pQ.woff2 +0 -0
  50. package/dist/client/_assets/chunks/1733f27476507ca68b68a803bc533cc2-DXL-14Ug.woff2 +0 -0
  51. package/dist/client/_assets/chunks/17c7f5d0a45e92ede0e5dec3a2c77efa-CNj3jQMl.woff2 +0 -0
  52. package/dist/client/_assets/chunks/1824321da801e6257b902f3d1d09054c-D4dgdyWB.woff2 +0 -0
  53. package/dist/client/_assets/chunks/188c2db794f3dd7a45889ddbc81da9bb-C9NbUBBO.woff2 +0 -0
  54. package/dist/client/_assets/chunks/1a08931435f885e707edb85978ea3bb6-BOiP2u7G.woff2 +0 -0
  55. package/dist/client/_assets/chunks/1a3a92c7c060a71a6c35819b9ebcdbb9-DFfonQfz.woff2 +0 -0
  56. package/dist/client/_assets/chunks/1be602ad456b0d75902d352116cb35fd-Bc-XQtQh.woff2 +0 -0
  57. package/dist/client/_assets/chunks/1cf737900dd49c2e88f1b3221a82a602-CfTmnns3.woff2 +0 -0
  58. package/dist/client/_assets/chunks/1fba7ec0e412e911bf31841de5a8a4e4-18ELMbFF.woff2 +0 -0
  59. package/dist/client/_assets/chunks/233d3a685ee18276b319363474599d47-BaFdJ0-G.woff2 +0 -0
  60. package/dist/client/_assets/chunks/2612c60f9dc5459ac42763591240a1d6-CTD69cq1.woff2 +0 -0
  61. package/dist/client/_assets/chunks/284b53bbefb06924cf236d24c8ed5641-CPDiuOv6.woff2 +0 -0
  62. package/dist/client/_assets/chunks/28a97af9ab9d38983d20f39ff09840e1-mFqcT3Jw.woff2 +0 -0
  63. package/dist/client/_assets/chunks/2a56eaf19d1d38a6b57e2a388f733676-CU52qBlE.woff2 +0 -0
  64. package/dist/client/_assets/chunks/2ab8cf1f23a5ac7939a7876054e711a7-B4T3dK7A.woff2 +0 -0
  65. package/dist/client/_assets/chunks/2abfbab82b6a7c04426afc054d2464da-D5PIIHde.woff2 +0 -0
  66. package/dist/client/_assets/chunks/2bfaadaf3479c72286248e6de0be0ec9-DEpU6O74.woff2 +0 -0
  67. package/dist/client/_assets/chunks/2c8c55e4cec85ce77e95cac9d330f5cf-BCTA96EZ.woff2 +0 -0
  68. package/dist/client/_assets/chunks/2e6f4bb71ef6b38765d51acc5a79f638-Mbs6PECD.woff2 +0 -0
  69. package/dist/client/_assets/chunks/2ed9981d2e8983365bd051159b976b6d-D8gg6NWM.woff2 +0 -0
  70. package/dist/client/_assets/chunks/2f4c633e923ba30c6ba376367379fc91-CIJCFztv.woff2 +0 -0
  71. package/dist/client/_assets/chunks/31194d303a67561926a544ed0e072aee-w3-eGYAi.woff2 +0 -0
  72. package/dist/client/_assets/chunks/31a197213ae61d7043c81013f52d10d6-DXZrrkJP.woff2 +0 -0
  73. package/dist/client/_assets/chunks/349965eee0a9b6c984a319ab96a4ece9-DByAVmXw.woff2 +0 -0
  74. package/dist/client/_assets/chunks/34e6750e00c3a911ef87dc66b336594d-DNbWesOu.woff2 +0 -0
  75. package/dist/client/_assets/chunks/3623d96698132c61e861f984ce11cc23-jqV9ErzX.woff2 +0 -0
  76. package/dist/client/_assets/chunks/36cda3eae13370b934bba4429fab9078-BPDcUlw9.woff2 +0 -0
  77. package/dist/client/_assets/chunks/36f3df4730cfca4fc77fe52b52e6a126-BwK10NC0.woff2 +0 -0
  78. package/dist/client/_assets/chunks/37887deb7a9c466cf2af6ee7ff372ea6-BrAerJB1.woff2 +0 -0
  79. package/dist/client/_assets/chunks/37dbd564820cce2f7b3331e442da0df3-Cql2kzIi.woff2 +0 -0
  80. package/dist/client/_assets/chunks/37dc8505b20b37a2477a9121b16d44a7-CoXw6XWV.woff2 +0 -0
  81. package/dist/client/_assets/chunks/39211b02f24c69cbd1b54f6d41b01933-4Xu7vvnq.woff2 +0 -0
  82. package/dist/client/_assets/chunks/3aa13f1843d7c8fa16109b6395a9a29d-BQFZUc3B.woff2 +0 -0
  83. package/dist/client/_assets/chunks/3aba89c4d4281553078de4622b498cbb-8QS1zDYk.woff2 +0 -0
  84. package/dist/client/_assets/chunks/3ac0a07878cacdc98bb889f40f5970d3-C-BaM6-y.woff2 +0 -0
  85. package/dist/client/_assets/chunks/3d88558097559d775231194d43745ba7-CR2aqGC7.woff2 +0 -0
  86. package/dist/client/_assets/chunks/3dc3e8c35524cad2d1236f3393104ccf-9sqBsF7B.woff2 +0 -0
  87. package/dist/client/_assets/chunks/3e24063b19abd2b051af986a6870046d-TlweM3SW.woff2 +0 -0
  88. package/dist/client/_assets/chunks/3e2c06bdd9dcab29aeaeb0cfb7fa608a-BaqU-dIt.woff2 +0 -0
  89. package/dist/client/_assets/chunks/424123f9371ba357087363263eeafcda-C8SVfEJi.woff2 +0 -0
  90. package/dist/client/_assets/chunks/43b3bbf43055c3e65db9cdf2c5b18c1c-CbHx6-Ki.woff2 +0 -0
  91. package/dist/client/_assets/chunks/4410f876d27c9821839474b6b1059fd7-ClYz91aY.woff2 +0 -0
  92. package/dist/client/_assets/chunks/44704471b60c18b3ecec5300a88f1102-DDmmy6-m.woff2 +0 -0
  93. package/dist/client/_assets/chunks/45140742dfa8686b58db2899ce89f89a-D5IMMzDM.woff2 +0 -0
  94. package/dist/client/_assets/chunks/46604090efbb99c2db1800928af0a239-JX2fpYX_.woff2 +0 -0
  95. package/dist/client/_assets/chunks/46998df85d31e629c0142f0556f5e7c5-BIkNG9Wh.woff2 +0 -0
  96. package/dist/client/_assets/chunks/469d2754e315e77270c12fa0ab28026c-D-y-vsfA.woff2 +0 -0
  97. package/dist/client/_assets/chunks/479329a39a3eb6c0047e6b0981919855-vZKCRx42.woff2 +0 -0
  98. package/dist/client/_assets/chunks/491bbba374093fff045f55803a22bfef-DSQXiDCd.woff2 +0 -0
  99. package/dist/client/_assets/chunks/49902f1c41b281f8eb3771abdd4dccb8-DxVxNg2W.woff2 +0 -0
  100. package/dist/client/_assets/chunks/4c0760284569b87d8f2290c324953973-Bqd-TKHH.woff2 +0 -0
  101. package/dist/client/_assets/chunks/4cba92c942694bdc479bc54101bb1aa4-BrfsrgrC.woff2 +0 -0
  102. package/dist/client/_assets/chunks/4d38e56738156625329d93bd5c8cc74a-DONkMP0y.woff2 +0 -0
  103. package/dist/client/_assets/chunks/4db4f31a16965baa42cb7dad667a8f04-Dt1Cy4gr.woff2 +0 -0
  104. package/dist/client/_assets/chunks/4e7d263c30bb48604069a3b4404d4eeb-DNPCkvKD.woff2 +0 -0
  105. package/dist/client/_assets/chunks/5279c7e465d9871edf51fd02e691eee7-CKhyhvVf.woff2 +0 -0
  106. package/dist/client/_assets/chunks/527ff336279465617aafbafe1a830632-CC3lv4pm.woff2 +0 -0
  107. package/dist/client/_assets/chunks/54bfdb6f21f9d6191e5100580239e14f-PGlqYvgl.woff2 +0 -0
  108. package/dist/client/_assets/chunks/56b40518ea0608e62826bd44ee27f5f9-BD9F9Oss.woff2 +0 -0
  109. package/dist/client/_assets/chunks/56be5e32fd01668fe5ba814d94905839-CSyFA-9l.woff2 +0 -0
  110. package/dist/client/_assets/chunks/572b9e28f1e6d89b765a16d809db84a1-BIYMjzEa.woff2 +0 -0
  111. package/dist/client/_assets/chunks/57e9a86651366c1ba299e47aa7e358c2-BHuZXB4W.woff2 +0 -0
  112. package/dist/client/_assets/chunks/57f89adeae55aa7fe2add3fc1801ce03-Cy3PNrfx.woff2 +0 -0
  113. package/dist/client/_assets/chunks/5921cef36750ff6092d94b83a802823b-hF-uymTh.woff2 +0 -0
  114. package/dist/client/_assets/chunks/59e95d8b5332dcdae894a19946ab767d-ClRfsjWZ.woff2 +0 -0
  115. package/dist/client/_assets/chunks/5acc7f736217259db79a42bf44241c48-FYQUuFOM.woff2 +0 -0
  116. package/dist/client/_assets/chunks/5c05f8b75c9d7f25b3c04ca711cf43b3-D-YWPkSq.woff2 +0 -0
  117. package/dist/client/_assets/chunks/5df9ce98c75f8c50fb4fff7f01a23925-DgoCENyW.woff2 +0 -0
  118. package/dist/client/_assets/chunks/5e0124c7265f1b4cf0fc797b94efbebc-BTBNHsSB.woff2 +0 -0
  119. package/dist/client/_assets/chunks/5ec349ff62b9ee5fe0c6dae867704dc4-B4dezhrz.woff2 +0 -0
  120. package/dist/client/_assets/chunks/60020a830d809c26970cf8e48ba1eeda-B6FDTSik.woff2 +0 -0
  121. package/dist/client/_assets/chunks/601f812e8b1eafecd5ba585a6a6d7962-gIVBlUU9.woff2 +0 -0
  122. package/dist/client/_assets/chunks/6025ddaf99d86cc8bfd781006b19fbd6-CS-ze337.woff2 +0 -0
  123. package/dist/client/_assets/chunks/60bcfa3ea7446eb42394aab02d28be40-C-PQfCu3.woff2 +0 -0
  124. package/dist/client/_assets/chunks/60f6e28b3f0400b0e8fe32c3c7116102-Dag9L2qU.woff2 +0 -0
  125. package/dist/client/_assets/chunks/615a7e0d63f257109d599b2a1977de68-D6Y93yoG.woff2 +0 -0
  126. package/dist/client/_assets/chunks/615b38e0b5bb3ea68426f44f47706e6f-B5yzIIer.woff2 +0 -0
  127. package/dist/client/_assets/chunks/61a2c80d0c924d5a6187c02e8d1d1642-BY3MmHbZ.woff2 +0 -0
  128. package/dist/client/_assets/chunks/62fc94f8d4c5a750b7f25e7387539910-3ql2_WDK.woff2 +0 -0
  129. package/dist/client/_assets/chunks/639c7c6b0b0c27c738702741cfa4b8c0-BCD3OGri.woff2 +0 -0
  130. package/dist/client/_assets/chunks/63fafcb069520613d0ea35ad3e6b1e42-DP0Rrj4o.woff2 +0 -0
  131. package/dist/client/_assets/chunks/64a404d675f1d726f0891b7a80794595-DU3L0I9s.woff2 +0 -0
  132. package/dist/client/_assets/chunks/65a60d87c64228d258a123cbe85f5f31-B4kbf-79.woff2 +0 -0
  133. package/dist/client/_assets/chunks/65e9c4585d71bf48a5c62f367010da16-CvzgzReN.woff2 +0 -0
  134. package/dist/client/_assets/chunks/68a3a4bf337f8a0722be76676e20b850-Dnaw8EPD.woff2 +0 -0
  135. package/dist/client/_assets/chunks/6968e889e18891d912809fe484c2e745-Bj1m5e0s.woff2 +0 -0
  136. package/dist/client/_assets/chunks/6abdd163d2c4b85698db8aa1ce149361-B5FVGJuo.woff2 +0 -0
  137. package/dist/client/_assets/chunks/6b75213eb0be40ce84241eb2bb438a2e-CqHmF-NR.woff2 +0 -0
  138. package/dist/client/_assets/chunks/70714891ad3fbfc3d5f10a8669dacc5a-BWk5nLFN.woff2 +0 -0
  139. package/dist/client/_assets/chunks/7158022889c6c177062ac85036e7af10-B3D4K22n.woff2 +0 -0
  140. package/dist/client/_assets/chunks/73b794d885b88f7befb7ea8ab1780a15-D0ZehnEI.woff2 +0 -0
  141. package/dist/client/_assets/chunks/743f290baf027d4626a86e22f3d44600-DDTDQdRC.woff2 +0 -0
  142. package/dist/client/_assets/chunks/7520ca6ca8c60eb1e62d50e71c8d30f1-CVRaGu2G.woff2 +0 -0
  143. package/dist/client/_assets/chunks/757019c01c5e2c2b0f54293cea3b5636-BTovUnoH.woff2 +0 -0
  144. package/dist/client/_assets/chunks/759a647b77791b1e98f99bc0ab5317a7-CrK6FIFz.woff2 +0 -0
  145. package/dist/client/_assets/chunks/761d84afca8d7f34eebefe538adba827-DRbiVuwX.woff2 +0 -0
  146. package/dist/client/_assets/chunks/76e07530323418ec723c5d7634c9bcca-DGtVJTTH.woff2 +0 -0
  147. package/dist/client/_assets/chunks/76e51630143b95b6322ef93ad330da7a-D-kV82C_.woff2 +0 -0
  148. package/dist/client/_assets/chunks/76f117a3baacda304a71965a17266a13-DFfvsFUm.woff2 +0 -0
  149. package/dist/client/_assets/chunks/773819b7b9b8fd404a929867c0fd677e-Bu1xCMZb.woff2 +0 -0
  150. package/dist/client/_assets/chunks/77747f17625a259fb405b7bbdd84ac4b-o_vNfh-6.woff2 +0 -0
  151. package/dist/client/_assets/chunks/7817dd16805145d8538ad57590f69f5a-DguSUP3O.woff2 +0 -0
  152. package/dist/client/_assets/chunks/788498548ddfb710d6ef3c7bff79fa97-3ffoQfRd.woff2 +0 -0
  153. package/dist/client/_assets/chunks/78ea6c40923ce95367e3517dd1e5a849-DvLDRhX9.woff2 +0 -0
  154. package/dist/client/_assets/chunks/79aa7c8c842c4a27cf57c0a3a1ffca5a-sDoCeoKI.woff2 +0 -0
  155. package/dist/client/_assets/chunks/7b59f0ec7792b18458dc5a361e37884c-DWvxkKmT.woff2 +0 -0
  156. package/dist/client/_assets/chunks/7c3945788a689a69356c1a622d69d48b-1JjIpss2.woff2 +0 -0
  157. package/dist/client/_assets/chunks/7cc72fd2c9105560422b6a67c6162945-Co3AF5_G.woff2 +0 -0
  158. package/dist/client/_assets/chunks/7d0ea0690e432462f4d05a23d720ec19-Dx7DK4C3.woff2 +0 -0
  159. package/dist/client/_assets/chunks/7dffa5c1bec57e0903fd62357401ff1a-DuQHmiiN.woff2 +0 -0
  160. package/dist/client/_assets/chunks/7e38ca789a9e76ac91d9256374e154f0-BtETgGRe.woff2 +0 -0
  161. package/dist/client/_assets/chunks/7f9a6c9286b68de9c72b0024f7beeb40-JpXdo4FC.woff2 +0 -0
  162. package/dist/client/_assets/chunks/815a0b797465190f60d8b1a04656c42e-Spzo94nU.woff2 +0 -0
  163. package/dist/client/_assets/chunks/823d1bb11097331238d103c7f72138dc-CGN6m2VF.woff2 +0 -0
  164. package/dist/client/_assets/chunks/82f6ccc063960eed1cdfd1d61ada8862-CsmAqx2a.woff2 +0 -0
  165. package/dist/client/_assets/chunks/835b6505bb9eea9678925a1fa885353d-C6mSBwUc.woff2 +0 -0
  166. package/dist/client/_assets/chunks/845b4b67564d62cf5cad242f7ac08ea5-DnvzrloJ.woff2 +0 -0
  167. package/dist/client/_assets/chunks/84c8e7bc0931008ed91f44ed12dfea94-C2Hvhh-b.woff2 +0 -0
  168. package/dist/client/_assets/chunks/8726558cf297cbda831cc19514897205-gSFV1vW0.woff2 +0 -0
  169. package/dist/client/_assets/chunks/8855472f3c02f6c7ebb3216617ebe4af-C1JsWVqq.woff2 +0 -0
  170. package/dist/client/_assets/chunks/887a11290ba78b1e66c6d2f67043005e-L_A1f5Fz.woff2 +0 -0
  171. package/dist/client/_assets/chunks/888ba53510213d5d1f6a7deb60e569b6-OP8E-ELN.woff2 +0 -0
  172. package/dist/client/_assets/chunks/88db1d042074fb6e66821ffc10941930-kZ6K3e62.woff2 +0 -0
  173. package/dist/client/_assets/chunks/892aa49b2529c89bb4076d4aa51fe30f-Bw0_Km2A.woff2 +0 -0
  174. package/dist/client/_assets/chunks/8b5c9ef81159f31d2ab35f45ca4373e0-Djw95sKC.woff2 +0 -0
  175. package/dist/client/_assets/chunks/8b91c7c2ed390f1278b9befa3aa87233-B2C3iFbQ.woff2 +0 -0
  176. package/dist/client/_assets/chunks/8dc18c0cebe6aa4bf4c45dbb831048ab-bUlANLit.woff2 +0 -0
  177. package/dist/client/_assets/chunks/8e231d707f0c4f8e3cde90a6b52a79aa-CvwbWlul.woff2 +0 -0
  178. package/dist/client/_assets/chunks/8fb45117a62d92dce44a80f0b729ead5-Djr4-o5-.woff2 +0 -0
  179. package/dist/client/_assets/chunks/9064c12fd72c7aba235d8c3881755b91-BnguxT-p.woff2 +0 -0
  180. package/dist/client/_assets/chunks/90b7848e9b1623b77bdcf155e90b839c-CFvPRnoM.woff2 +0 -0
  181. package/dist/client/_assets/chunks/911b9e53e9814de2998c60bf3115f560-DmPmj635.woff2 +0 -0
  182. package/dist/client/_assets/chunks/919a879bd2d580d8491a31a449390689-BY2w-8_Y.woff2 +0 -0
  183. package/dist/client/_assets/chunks/91da6cb174bebfb96976e2f1316be2fe-DztAJb6W.woff2 +0 -0
  184. package/dist/client/_assets/chunks/92fdc376bce277874e75db666f175b44-DJUnHEIN.woff2 +0 -0
  185. package/dist/client/_assets/chunks/930a23430f0eb64480d7fe5f82834e21-2VrDPT7E.woff2 +0 -0
  186. package/dist/client/_assets/chunks/934dfdbed2bb2c4b6129199d699a34fa-CtpHsjRD.woff2 +0 -0
  187. package/dist/client/_assets/chunks/93bed0e5f2dd5a21cf73304fcfbc149d-DJiaETX7.woff2 +0 -0
  188. package/dist/client/_assets/chunks/93f3ea6918533d96d9c252378b9a4af0-D-FJaGi-.woff2 +0 -0
  189. package/dist/client/_assets/chunks/95a0951d2a2722ff773a1a45e8c474d6-DuZg-BpP.woff2 +0 -0
  190. package/dist/client/_assets/chunks/95ff41191c76ef893ac70a1043e95e44-C08QDbJO.woff2 +0 -0
  191. package/dist/client/_assets/chunks/96c75862c8cec51a7c05ff025fea86cd-DBnRwCuW.woff2 +0 -0
  192. package/dist/client/_assets/chunks/97d7a17234f2b5030ae9697ae00aded2-CkUSaB-p.woff2 +0 -0
  193. package/dist/client/_assets/chunks/982cd1765ca6bbb3e6547e47834d5148-DkIybx1r.woff2 +0 -0
  194. package/dist/client/_assets/chunks/985d4d41afa0934a4eb2de35473fb9a2-CF7rgJMw.woff2 +0 -0
  195. package/dist/client/_assets/chunks/995c4fda62d25c3b79dd5987737df6ae-CY5ri8HW.woff2 +0 -0
  196. package/dist/client/_assets/chunks/9b0c3caac458c7bc9c9133e40405ddea-DhPrYOcz.woff2 +0 -0
  197. package/dist/client/_assets/chunks/9b21e8244f5930c48ad5073f83777b6c-CuE9-r-r.woff2 +0 -0
  198. package/dist/client/_assets/chunks/9bb38fd05201de666b88b82d56a386f7-nMl6lB8X.woff2 +0 -0
  199. package/dist/client/_assets/chunks/9d0afa53dc2f92ba42024f55f11f6779-Bozpx8bL.woff2 +0 -0
  200. package/dist/client/_assets/chunks/9e13a47242926f1cd7d68c08d9ef8889-DwKQjc8M.woff2 +0 -0
  201. package/dist/client/_assets/chunks/9eff2ab2a9d2ced47ab761bf6fdb11ec-D86-Zm1H.woff2 +0 -0
  202. package/dist/client/_assets/chunks/9f1963fe8d4d6878d717d872a8f3fdb5-DxjITUFq.woff2 +0 -0
  203. package/dist/client/_assets/chunks/9f3bdab4025dc7c5ba1a5871e62e8918-BqxDEFLJ.woff2 +0 -0
  204. package/dist/client/_assets/chunks/9fce02d1a09f464c36c9fcfb14a354c5-F2tMAB_j.woff2 +0 -0
  205. package/dist/client/_assets/chunks/a00e94d4f04df6aa659db9c4954c7efe-CRBKCj_3.woff2 +0 -0
  206. package/dist/client/_assets/chunks/a0a6755c7e3f9e4cf03387027dd9f16c-BAyRqF2w.woff2 +0 -0
  207. package/dist/client/_assets/chunks/a0d00fe4816c95a8c7dffd445ef00b03-CxcTiRLJ.woff2 +0 -0
  208. package/dist/client/_assets/chunks/a0f199077fa1a33bc2a1e01c64de7fe6-IdgVJmZD.woff2 +0 -0
  209. package/dist/client/_assets/chunks/a2b0597837e382aca19919c4b74e58c8-7iVOBjzI.woff2 +0 -0
  210. package/dist/client/_assets/chunks/a373e85c96aa0dc303092429fb837e51-Dvmw8mbX.woff2 +0 -0
  211. package/dist/client/_assets/chunks/a379ada89abd750c587ded29f77e731c-CgPBsEsv.woff2 +0 -0
  212. package/dist/client/_assets/chunks/a4475c442c6b259354c9eda62e64e7df-TZ0c8vK-.woff2 +0 -0
  213. package/dist/client/_assets/chunks/a48be56ce5d3a99dc8f8331398004412-BR_ixH1W.woff2 +0 -0
  214. package/dist/client/_assets/chunks/a4a39b5f048671aa37c2b2a9581ab08a-EDfDYsUd.woff2 +0 -0
  215. package/dist/client/_assets/chunks/a534449d67561ac9b06489f64e57ad98-C355TCrD.woff2 +0 -0
  216. package/dist/client/_assets/chunks/a6e044424780a57610833cc856c15bf5-x1b8FE4H.woff2 +0 -0
  217. package/dist/client/_assets/chunks/a7c35e42a659347e490c3cb7983b2e7f-BWpA0-oz.woff2 +0 -0
  218. package/dist/client/_assets/chunks/a7c58070d68dc1724e9d4e781afb78db-ql27pT-6.woff2 +0 -0
  219. package/dist/client/_assets/chunks/a806759e72987951d6d08b7cbdf36a1b-TvLDwB_u.woff2 +0 -0
  220. package/dist/client/_assets/chunks/a88481ec8bc7be11cb66e46781a79bb9-Ck-r_sGx.woff2 +0 -0
  221. package/dist/client/_assets/chunks/a919a8c6fec6d7e8bb21177965f2e9d7-CC5uWzwd.woff2 +0 -0
  222. package/dist/client/_assets/chunks/a9a016d4a93409f65278327e8a8bb38d-dylrNwF8.woff2 +0 -0
  223. package/dist/client/_assets/chunks/aa662092cf249707123ca4d575e2764b-CSxNsTUL.woff2 +0 -0
  224. package/dist/client/_assets/chunks/aae1eda193285ab817a2d1b408440326-CjyjBoRm.woff2 +0 -0
  225. package/dist/client/_assets/chunks/ab92d41062a7644faa45f50bf384454a-Cq0X8a9H.woff2 +0 -0
  226. package/dist/client/_assets/chunks/accc43762ad05edfbff66ba1380d192a-B5jIykaZ.woff2 +0 -0
  227. package/dist/client/_assets/chunks/ad47bcd6d4663026d648c132d969318d-BYdIbDge.woff2 +0 -0
  228. package/dist/client/_assets/chunks/ad67113f88b59582991cb0c5d33ea19f-DOH1vcIZ.woff2 +0 -0
  229. package/dist/client/_assets/chunks/adb6e613fb99c6dd660b727101f554a8-DJDw_ZZK.woff2 +0 -0
  230. package/dist/client/_assets/chunks/adc4a98a89870ef984ee8f4b57c8a3a5-D30vw26R.woff2 +0 -0
  231. package/dist/client/_assets/chunks/af57a2e8c72f9ba0efb1af771d32c124-D5Zwq9pS.woff2 +0 -0
  232. package/dist/client/_assets/chunks/b0ae5f374e43dac992a4a5d334cebc0b-Ccoi4dPt.woff2 +0 -0
  233. package/dist/client/_assets/chunks/b0cb664cb2e1371efda8943c0b7dcd1c-YzGFnelF.woff2 +0 -0
  234. package/dist/client/_assets/chunks/b0d1cdced482352cf0d3ae58638aacb9-IunWfc6R.woff2 +0 -0
  235. package/dist/client/_assets/chunks/b12379ab782468d725519cd07a7d15bc-IjbydKs2.woff2 +0 -0
  236. package/dist/client/_assets/chunks/b320f5d185b2cff933ac549c184031c5-IYnPrIz4.woff2 +0 -0
  237. package/dist/client/_assets/chunks/b326a8a9c33b554db570da94f60bc380-B6dzaCQg.woff2 +0 -0
  238. package/dist/client/_assets/chunks/b55486f8a459c838fe329d4e79a8c211-DW8q7oGV.woff2 +0 -0
  239. package/dist/client/_assets/chunks/b6117ff2993b11bb1fdc7ea3588a010c-JoMYL5W_.woff2 +0 -0
  240. package/dist/client/_assets/chunks/b61982951bd51b724143c30dfaaa9fe9-D3KhBKl6.woff2 +0 -0
  241. package/dist/client/_assets/chunks/b91234c10fd8b8c8abc88e03afe66a1f-BABNh2la.woff2 +0 -0
  242. package/dist/client/_assets/chunks/b9317198f118a1dfd8ddf2b82ec028f3-CiMecxeH.woff2 +0 -0
  243. package/dist/client/_assets/chunks/ba825ae79b1a6f7e0cce5215fcb5c96f-B6EWU_D5.woff2 +0 -0
  244. package/dist/client/_assets/chunks/ba9c59d7dfa4494db1bb764ada81467d-D0yPcC1m.woff2 +0 -0
  245. package/dist/client/_assets/chunks/bab547459d514f46206e340c4bb2dc88-YnBqlGhw.woff2 +0 -0
  246. package/dist/client/_assets/chunks/bb0b15492b8cdbbec57c0bcfa0aa9241-BjIFbEsN.woff2 +0 -0
  247. package/dist/client/_assets/chunks/bc6183ac08f0fac78c46f80c10cf7c92-B81nbRb3.woff2 +0 -0
  248. package/dist/client/_assets/chunks/bc8ccea5abf6598cf3cfa97eb59804bb-B_7IbluG.woff2 +0 -0
  249. package/dist/client/_assets/chunks/bc95a792cbca6639214c9b0da13392ff-CHQe0E-D.woff2 +0 -0
  250. package/dist/client/_assets/chunks/bcc39eda837bb7a7a3d37c8c60fffb81-CGd1Zk6S.woff2 +0 -0
  251. package/dist/client/_assets/chunks/bcfa62f35731856246c146d3a6932bf3-DMDq6w4v.woff2 +0 -0
  252. package/dist/client/_assets/chunks/bd73264d7f98776708d5d6f3c9b78fcc-d9QWWMym.woff2 +0 -0
  253. package/dist/client/_assets/chunks/bed610b217d500f5975cfc9fe6157570-B5t7s1bw.woff2 +0 -0
  254. package/dist/client/_assets/chunks/bedc74b423b7293b6ad0bdecc61c42cc-4Nnh7peP.woff2 +0 -0
  255. package/dist/client/_assets/chunks/bf8901f8f11d4f433ea17dabc9370ea6-B8dkfvGn.woff2 +0 -0
  256. package/dist/client/_assets/chunks/bfb00d4a4c48661bd0be99f300a0faae-OxsWZ7l-.woff2 +0 -0
  257. package/dist/client/_assets/chunks/c0098958e20db68cab90097b5e62516f-VpjQo3vV.woff2 +0 -0
  258. package/dist/client/_assets/chunks/c063897793f593eb26d6ff7b7baaba18-BWeJJ1sH.woff2 +0 -0
  259. package/dist/client/_assets/chunks/c1b0df29ae41d764904df84e9ac83d1e-D1N934vs.woff2 +0 -0
  260. package/dist/client/_assets/chunks/c4143bb9f2fe77b6ccf20088a8904650-nHfYM24e.woff2 +0 -0
  261. package/dist/client/_assets/chunks/c42c67070cfe99cf823df92d81c7fa6e-B_a405Ez.woff2 +0 -0
  262. package/dist/client/_assets/chunks/c4bfaa5e50798246e3770718b7a7c84a-B-r6Y_o0.woff2 +0 -0
  263. package/dist/client/_assets/chunks/c4d749e45ecd5a5aed5a0bb3ebfd355d-CkqE1fRi.woff2 +0 -0
  264. package/dist/client/_assets/chunks/c6c2971ad1f3221f6cf84028aa0f477e-D7BZGIcY.woff2 +0 -0
  265. package/dist/client/_assets/chunks/c7ff3f6bbdcd5f604b7343602ab904df-Bq7UjVrZ.woff2 +0 -0
  266. package/dist/client/_assets/chunks/c84598999133455503042e06f4ab79cb-DIXndJX3.woff2 +0 -0
  267. package/dist/client/_assets/chunks/c945c62368357d05a53206620460fb30-82gFEf6n.woff2 +0 -0
  268. package/dist/client/_assets/chunks/c96d83978add28b356c22c4c84916733-BL4VaPIE.woff2 +0 -0
  269. package/dist/client/_assets/chunks/c97f41eef722121d86f55d553c056a39-CjejgQO2.woff2 +0 -0
  270. package/dist/client/_assets/chunks/ca62704509932d3232d62918de97af3f-CKWF-3mX.woff2 +0 -0
  271. package/dist/client/_assets/chunks/ca9a533988d7019597a60d4e17127e0c-ClZ6ygZM.woff2 +0 -0
  272. package/dist/client/_assets/chunks/cb7ccd6494256f7a2977b7c2b0225592-Cxp8XObm.woff2 +0 -0
  273. package/dist/client/_assets/chunks/ce022e18a1377ac509443c3c3790b431-DpH0t6Tn.woff2 +0 -0
  274. package/dist/client/_assets/chunks/ce41d70ce6a069a498525c9e15c45cf2-DGtgv-_b.woff2 +0 -0
  275. package/dist/client/_assets/chunks/cf2b28f90f47276f7e2688a65e88a101-LVbNkXHj.woff2 +0 -0
  276. package/dist/client/_assets/chunks/cf9cffe56636322f62b40d61130fbc5e-DNCBMqAo.woff2 +0 -0
  277. package/dist/client/_assets/chunks/d1f064825fa5784b5c930652bd831cce-BU6qwjp8.woff2 +0 -0
  278. package/dist/client/_assets/chunks/d20d8944bc0b85f5b2aae4b24f343516-oY9l-NmC.woff2 +0 -0
  279. package/dist/client/_assets/chunks/d2faabcedd19f016e7b21bce073c0ec8-Dv6eFCf5.woff2 +0 -0
  280. package/dist/client/_assets/chunks/d320171e57480510f87dbbc7d5264b0a-CcCfjaFX.woff2 +0 -0
  281. package/dist/client/_assets/chunks/d36644d6502527e1fff205d0c7eca434-kVBj8zy_.woff2 +0 -0
  282. package/dist/client/_assets/chunks/d3a72a99d365dddfbca8d017a8011368-Bgdmhl3M.woff2 +0 -0
  283. package/dist/client/_assets/chunks/d40ab99c7a38026f411c8f112f742b48-D7Nm7fg6.woff2 +0 -0
  284. package/dist/client/_assets/chunks/d42aafa0f246ad3b4c16fe96d3a3a432-CRxTqZXU.woff2 +0 -0
  285. package/dist/client/_assets/chunks/d4aaf23c13ae808a4bed617afd13aa07-d_T1SKe9.woff2 +0 -0
  286. package/dist/client/_assets/chunks/d509ee5c8241bc7c3de2039d75564fa5-BpNFD9JE.woff2 +0 -0
  287. package/dist/client/_assets/chunks/d675d717cd329bfd4c0524f76ae1579c-Bd5_ctoQ.woff2 +0 -0
  288. package/dist/client/_assets/chunks/d689b1861d7e4377dd72ad3013482612-jPsVOU3F.woff2 +0 -0
  289. package/dist/client/_assets/chunks/d6bb686bddfbe8f38a36a68e609a8667-C_QduS-X.woff2 +0 -0
  290. package/dist/client/_assets/chunks/d9020ff69a83b2a6ee1f42ae480f7db0-DLBUPMCe.woff2 +0 -0
  291. package/dist/client/_assets/chunks/d9047070d72a816b3dba9d40c2d85e69-LTQ42vEp.woff2 +0 -0
  292. package/dist/client/_assets/chunks/da04549f3f4ed28076b01b8cd710d313-CDXOeduw.woff2 +0 -0
  293. package/dist/client/_assets/chunks/da7af303f8c645f9a9dbae0e6e32dd35-DCkS2jqi.woff2 +0 -0
  294. package/dist/client/_assets/chunks/da90a9012ab2d98f759e3fa0820ef502-D0D9d1zg.woff2 +0 -0
  295. package/dist/client/_assets/chunks/dc6e234ded795e91f76d6647f628fbf0-IkooXcRh.woff2 +0 -0
  296. package/dist/client/_assets/chunks/dc8f6256445e68199540be9ade33529f-umb5tWbZ.woff2 +0 -0
  297. package/dist/client/_assets/chunks/dd0cfdfdac0866e66d587e2b5a9e9961-bl4ozE0c.woff2 +0 -0
  298. package/dist/client/_assets/chunks/de304c8a02e45ded4f8dcc479d167198-ClRu89-4.woff2 +0 -0
  299. package/dist/client/_assets/chunks/df10d94bea357a43313e20da5d84bd30-CmAgh2oA.woff2 +0 -0
  300. package/dist/client/_assets/chunks/df9568257eb29b156449fdd4bec5ec76-BMz6aLRr.woff2 +0 -0
  301. package/dist/client/_assets/chunks/e067cd0ed76c90cd0a93c9339253f20b-6RxCQ4jy.woff2 +0 -0
  302. package/dist/client/_assets/chunks/e08b07772e7bed3cec2832d43f7fd339-CQCkeKsr.woff2 +0 -0
  303. package/dist/client/_assets/chunks/e12150d5a39b30be8f567968c7a527b0-DjdoWzeQ.woff2 +0 -0
  304. package/dist/client/_assets/chunks/e34b2b141e472dc776c86fdf8eea23b0-Cjk7H-Ev.woff2 +0 -0
  305. package/dist/client/_assets/chunks/e444fd88f1390636e603d0d681538ac8-Bx7CPOZh.woff2 +0 -0
  306. package/dist/client/_assets/chunks/e53fcb2381eee345db4f6f973dd95a3e-DmkkCUWz.woff2 +0 -0
  307. package/dist/client/_assets/chunks/e5bd313ef81f687d398aacb11cec3069-CwBEbcji.woff2 +0 -0
  308. package/dist/client/_assets/chunks/e79898628283edc27180fc39d0d769c1-By2pplQz.woff2 +0 -0
  309. package/dist/client/_assets/chunks/e8c15be62faf978d208925e79ea6a10d-Ct5M9lTP.woff2 +0 -0
  310. package/dist/client/_assets/chunks/e8c364c16daa04835bf32d293d2598db-3v4Tut_d.woff2 +0 -0
  311. package/dist/client/_assets/chunks/e91c4d941ed9f5c2f3e27f205a3a225e-BwOJ2Lst.woff2 +0 -0
  312. package/dist/client/_assets/chunks/e9c566be7a5d38a9085225f7372bc82b-BCtnCVML.woff2 +0 -0
  313. package/dist/client/_assets/chunks/e9ebc567a711eeb29019ddae3e0ce7fe-CnNhV49O.woff2 +0 -0
  314. package/dist/client/_assets/chunks/eaf332445a40942928e5f5750c1c3116-qZ5LSRrj.woff2 +0 -0
  315. package/dist/client/_assets/chunks/eb5afb3d952b8593782caec6026514b6-BXugZF2T.woff2 +0 -0
  316. package/dist/client/_assets/chunks/ec86e23683052da5cfdc3b77641bd15a-BeLcbcRc.woff2 +0 -0
  317. package/dist/client/_assets/chunks/ecb8875a56c7b038b35432fda41ae128-BVqgNf1o.woff2 +0 -0
  318. package/dist/client/_assets/chunks/eda1b0cb6d1719dd9bedcf3216a9e8de-BJTfLL5z.woff2 +0 -0
  319. package/dist/client/_assets/chunks/edd6a4f608d04fc0351d7688cfc321e4-d3V4U40c.woff2 +0 -0
  320. package/dist/client/_assets/chunks/ee6fce20b420a480714607c66d7f97e5-DEtC-G5G.woff2 +0 -0
  321. package/dist/client/_assets/chunks/eee3836d6ac17ebb2c450bbcbc9db121-C5a2TZjQ.woff2 +0 -0
  322. package/dist/client/_assets/chunks/et-book-bold-line-figures-BFJr2_zv.woff +0 -0
  323. package/dist/client/_assets/chunks/et-book-display-italic-old-style-figures-CoeRJAe8.woff +0 -0
  324. package/dist/client/_assets/chunks/et-book-roman-line-figures-CaA40oOf.woff +0 -0
  325. package/dist/client/_assets/chunks/f1661731474b78bdf81114daf10b254f-KnMKX_U3.woff2 +0 -0
  326. package/dist/client/_assets/chunks/f2c58dee206ba9355046fc23d05491f7-Cm4tCWav.woff2 +0 -0
  327. package/dist/client/_assets/chunks/f50ac27ea4358d67fdda403c2bb52467-WCfYHde1.woff2 +0 -0
  328. package/dist/client/_assets/chunks/f55334112f8e8be82d65db29887a663f-vDq1zH8k.woff2 +0 -0
  329. package/dist/client/_assets/chunks/f5738255e92d8dd34a46d1bcdf4c4074-tpghTzL1.woff2 +0 -0
  330. package/dist/client/_assets/chunks/f740e93f3c0277ecc616594103bca683-C4BgW_ko.woff2 +0 -0
  331. package/dist/client/_assets/chunks/f7633b5af033d76ff2fb3c3c266d77c5-BCkrZxwO.woff2 +0 -0
  332. package/dist/client/_assets/chunks/f7d8468cba2335a83ee414ea68291bab-CVnotkZU.woff2 +0 -0
  333. package/dist/client/_assets/chunks/f91cd722855f4269256eae1187df64ec-DJlAx1A_.woff2 +0 -0
  334. package/dist/client/_assets/chunks/f9695c6c4df2bf6bc03045ff79d4f01f-DDaYGYHT.woff2 +0 -0
  335. package/dist/client/_assets/chunks/fa6e58ce4b52695e7ae19bbea6336ec8-B30jEpze.woff2 +0 -0
  336. package/dist/client/_assets/chunks/fada6eaa68ff8816afe43d2a36c5423e-DlGAWNPb.woff2 +0 -0
  337. package/dist/client/_assets/chunks/fbcc4bf5367218951172bdee6f77d7a6-Dax2VSCd.woff2 +0 -0
  338. package/dist/client/_assets/chunks/fc895f5ce66b656f4a933097bf2a8775-DjjysNdf.woff2 +0 -0
  339. package/dist/client/_assets/chunks/fc9ae5b600fb711f2d67e93ce768cba4-D1lx7LBz.woff2 +0 -0
  340. package/dist/client/_assets/chunks/fca6720fd14c467d29a90f18ef3859b9-B3btrwq3.woff2 +0 -0
  341. package/dist/client/_assets/chunks/fd1bb507bcbf04856eeb7f7fd47ea579-NR1nGJTt.woff2 +0 -0
  342. package/dist/client/_assets/chunks/fd1bceb55d3e0183ac2454b8532fec7d-D3t-v6nn.woff2 +0 -0
  343. package/dist/client/_assets/chunks/fd2069e6e8588c70b4f11364093b81f2-BoXhYVgw.woff2 +0 -0
  344. package/dist/client/_assets/chunks/{url-CG0eolsk.js → url-pLre2DM_.js} +1 -1
  345. package/dist/client/_assets/client-C_kImWZj.css +2 -0
  346. package/dist/client/_assets/client-D95FNDg5.js +272 -0
  347. package/dist/client/_assets/client-auth-BRFl5zQA.js +4547 -0
  348. package/dist/client/_assets/client-cjk-jp-DZwrTzQC.css +1 -0
  349. package/dist/client/_assets/client-cjk-kr-_3ZNI2ZP.css +1 -0
  350. package/dist/env-wCpMcNXs.js +252 -0
  351. package/dist/github-api-CficQztC.js +176 -0
  352. package/dist/github-app-F4qZ05xk.js +275 -0
  353. package/dist/github-sync-zohnA9qv.js +4696 -0
  354. package/dist/index.js +88 -2
  355. package/dist/node.js +71 -30
  356. package/dist/url-FvvgARU9.js +305 -0
  357. package/package.json +16 -12
  358. package/src/__tests__/bin/content-cli.test.ts +179 -0
  359. package/src/__tests__/bin/media-cli.test.ts +192 -0
  360. package/src/__tests__/export-hugo-build.test.ts +178 -0
  361. package/src/__tests__/export-import-roundtrip.test.ts +265 -0
  362. package/src/__tests__/export-service.test.ts +763 -510
  363. package/src/__tests__/helpers/export-fixtures.ts +108 -0
  364. package/src/__tests__/import-site-command.test.ts +358 -350
  365. package/src/__tests__/site-pull-media.test.ts +256 -0
  366. package/src/app.tsx +65 -13
  367. package/src/client/__tests__/collection-form-bridge.test.ts +87 -7
  368. package/src/client/__tests__/collection-page-actions.test.ts +4 -4
  369. package/src/client/__tests__/collection-picker-order.test.ts +53 -0
  370. package/src/client/__tests__/collection-sort-menu.test.ts +2 -2
  371. package/src/client/__tests__/compose-bridge.test.ts +305 -13
  372. package/src/client/__tests__/compose-launch.test.ts +120 -0
  373. package/src/client/__tests__/compose-shortcuts.test.ts +141 -0
  374. package/src/client/__tests__/feed-video-player.test.ts +44 -0
  375. package/src/client/__tests__/site-header-nav.test.ts +250 -0
  376. package/src/client/__tests__/sortable-list.test.ts +6 -2
  377. package/src/client/__tests__/thread-context.test.ts +117 -0
  378. package/src/client/collection-form-bridge.ts +109 -4
  379. package/src/client/collection-page-actions.ts +3 -1
  380. package/src/client/collection-picker-order.ts +104 -0
  381. package/src/client/components/__tests__/{jant-collection-sidebar.test.ts → jant-collection-directory.test.ts} +145 -8
  382. package/src/client/components/__tests__/jant-collection-form.test.ts +40 -18
  383. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1285 -66
  384. package/src/client/components/__tests__/jant-compose-editor.test.ts +254 -19
  385. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +127 -1
  386. package/src/client/components/__tests__/jant-media-lightbox.test.ts +237 -0
  387. package/src/client/components/__tests__/jant-nav-manager.test.ts +326 -0
  388. package/src/client/components/__tests__/jant-post-menu.test.ts +372 -2
  389. package/src/client/components/__tests__/jant-settings-avatar.test.ts +61 -5
  390. package/src/client/components/__tests__/jant-settings-general.test.ts +29 -28
  391. package/src/client/components/__tests__/jant-text-preview.test.ts +206 -0
  392. package/src/client/components/collection-manager-types.ts +18 -1
  393. package/src/client/components/compose-types.ts +48 -0
  394. package/src/client/components/jant-collection-directory.ts +1448 -0
  395. package/src/client/components/jant-collection-form.ts +65 -15
  396. package/src/client/components/jant-command-palette.ts +544 -0
  397. package/src/client/components/jant-compose-dialog.ts +2490 -375
  398. package/src/client/components/jant-compose-editor.ts +420 -75
  399. package/src/client/components/jant-compose-fullscreen.ts +59 -8
  400. package/src/client/components/jant-media-lightbox.ts +373 -18
  401. package/src/client/components/jant-nav-manager.ts +624 -286
  402. package/src/client/components/jant-post-menu.ts +451 -126
  403. package/src/client/components/jant-repo-picker-types.ts +39 -0
  404. package/src/client/components/jant-repo-picker.ts +799 -0
  405. package/src/client/components/jant-settings-avatar.ts +16 -2
  406. package/src/client/components/jant-settings-general.ts +137 -35
  407. package/src/client/components/jant-text-preview.ts +239 -55
  408. package/src/client/components/nav-manager-types.ts +25 -9
  409. package/src/client/components/settings-types.ts +13 -2
  410. package/src/client/compose-bridge.ts +428 -68
  411. package/src/client/compose-launch.ts +41 -4
  412. package/src/client/compose-shortcuts.ts +77 -5
  413. package/src/client/feed-video-player.ts +374 -0
  414. package/src/client/image-processor.ts +53 -127
  415. package/src/client/media-lightbox-events.ts +1 -0
  416. package/src/client/media-scroll-hint.ts +66 -0
  417. package/src/client/multipart-upload.ts +4 -1
  418. package/src/client/palette-shortcuts.ts +35 -0
  419. package/src/client/search-rank.ts +74 -0
  420. package/src/client/settings-bridge.ts +7 -0
  421. package/src/client/site-header-nav.d.ts +1 -0
  422. package/src/client/site-header-nav.js +139 -51
  423. package/src/client/sortable-list.ts +2 -0
  424. package/src/client/thread-context.ts +54 -11
  425. package/src/client/tiptap/__tests__/block-insertion.test.ts +65 -0
  426. package/src/client/tiptap/__tests__/footnotes.test.ts +692 -0
  427. package/src/client/tiptap/__tests__/link-toolbar.test.ts +120 -4
  428. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +48 -0
  429. package/src/client/tiptap/block-insertion.ts +36 -0
  430. package/src/client/tiptap/bubble-menu.ts +34 -4
  431. package/src/client/tiptap/create-editor.ts +49 -30
  432. package/src/client/tiptap/embed-dialog.ts +301 -0
  433. package/src/client/tiptap/embed-node.ts +373 -0
  434. package/src/client/tiptap/embed-paste.ts +91 -0
  435. package/src/client/tiptap/extensions.ts +77 -28
  436. package/src/client/tiptap/footnotes.ts +718 -0
  437. package/src/client/tiptap/html-block-node.ts +222 -0
  438. package/src/client/tiptap/image-node.ts +49 -6
  439. package/src/client/tiptap/inline-image-upload.ts +200 -40
  440. package/src/client/tiptap/link-input-rules.ts +50 -16
  441. package/src/client/tiptap/link-toolbar.ts +156 -133
  442. package/src/client/tiptap/markdown-clipboard.ts +120 -0
  443. package/src/client/tiptap/more-break.ts +36 -3
  444. package/src/client/tiptap/paste-media.ts +6 -0
  445. package/src/client/tiptap/slash-commands.ts +167 -14
  446. package/src/client/tiptap/tab-indent.ts +184 -0
  447. package/src/client/tiptap/toolbar-mode.ts +15 -2
  448. package/src/client/tiptap/wrapping-input-rules.ts +102 -0
  449. package/src/client/types/sortablejs.d.ts +9 -1
  450. package/src/client/upload-session.ts +2 -1
  451. package/src/client/video-processor.ts +177 -20
  452. package/src/client-auth.ts +9 -1
  453. package/src/client-site.ts +15 -0
  454. package/src/client.ts +2 -0
  455. package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
  456. package/src/db/__tests__/migration-rehearsal.test.ts +5 -1
  457. package/src/db/__tests__/migrations.test.ts +25 -2
  458. package/src/db/backfills/0001_strip_collection_path_c_prefix.sql +11 -0
  459. package/src/db/backfills/0002_strip_collection_path_c_prefix_fix.sql +12 -0
  460. package/src/db/backfills/0003_clear_system_nav_default_labels.sql +15 -0
  461. package/src/db/dialect.ts +40 -0
  462. package/src/db/migrations/0005_busy_the_phantom.sql +39 -0
  463. package/src/db/migrations/0006_adorable_magdalene.sql +3 -0
  464. package/src/db/migrations/0007_unusual_warstar.sql +1 -0
  465. package/src/db/migrations/0008_nasty_lockheed.sql +1 -0
  466. package/src/db/migrations/0009_clear_fixer.sql +31 -0
  467. package/src/db/migrations/0010_futuristic_preak.sql +31 -0
  468. package/src/db/migrations/0011_bizarre_smasher.sql +30 -0
  469. package/src/db/migrations/0012_furry_thena.sql +52 -0
  470. package/src/db/migrations/0013_mixed_lightspeed.sql +39 -0
  471. package/src/db/migrations/0014_high_the_santerians.sql +41 -0
  472. package/src/db/migrations/0015_skinny_shinobi_shaw.sql +1 -0
  473. package/src/db/migrations/0016_remarkable_nicolaos.sql +17 -0
  474. package/src/db/migrations/0017_powerful_moonstone.sql +14 -0
  475. package/src/db/migrations/meta/0005_snapshot.json +1904 -0
  476. package/src/db/migrations/meta/0006_snapshot.json +1925 -0
  477. package/src/db/migrations/meta/0007_snapshot.json +1933 -0
  478. package/src/db/migrations/meta/0008_snapshot.json +1940 -0
  479. package/src/db/migrations/meta/0009_snapshot.json +1952 -0
  480. package/src/db/migrations/meta/0010_snapshot.json +1952 -0
  481. package/src/db/migrations/meta/0011_snapshot.json +1948 -0
  482. package/src/db/migrations/meta/0012_snapshot.json +1955 -0
  483. package/src/db/migrations/meta/0013_snapshot.json +1977 -0
  484. package/src/db/migrations/meta/0014_snapshot.json +1988 -0
  485. package/src/db/migrations/meta/0015_snapshot.json +1995 -0
  486. package/src/db/migrations/meta/0016_snapshot.json +2104 -0
  487. package/src/db/migrations/meta/0017_snapshot.json +2188 -0
  488. package/src/db/migrations/meta/_journal.json +92 -1
  489. package/src/db/migrations/pg/0003_motionless_norrin_radd.sql +21 -0
  490. package/src/db/migrations/pg/0004_nervous_captain_midlands.sql +3 -0
  491. package/src/db/migrations/pg/0005_romantic_mesmero.sql +1 -0
  492. package/src/db/migrations/pg/0006_perpetual_bruce_banner.sql +1 -0
  493. package/src/db/migrations/pg/0007_nav_item_placement.sql +1 -0
  494. package/src/db/migrations/pg/0008_yielding_frightful_four.sql +3 -0
  495. package/src/db/migrations/pg/0009_outstanding_ogun.sql +1 -0
  496. package/src/db/migrations/pg/0010_overjoyed_gertrude_yorkes.sql +28 -0
  497. package/src/db/migrations/pg/0011_fixed_hulk.sql +19 -0
  498. package/src/db/migrations/pg/0012_cute_shockwave.sql +2 -0
  499. package/src/db/migrations/pg/0013_bizarre_obadiah_stane.sql +1 -0
  500. package/src/db/migrations/pg/0014_tearful_grim_reaper.sql +16 -0
  501. package/src/db/migrations/pg/0015_daffy_mikhail_rasputin.sql +14 -0
  502. package/src/db/migrations/pg/meta/0003_snapshot.json +2482 -0
  503. package/src/db/migrations/pg/meta/0004_snapshot.json +2500 -0
  504. package/src/db/migrations/pg/meta/0005_snapshot.json +2507 -0
  505. package/src/db/migrations/pg/meta/0006_snapshot.json +2513 -0
  506. package/src/db/migrations/pg/meta/0008_snapshot.json +2524 -0
  507. package/src/db/migrations/pg/meta/0009_snapshot.json +2520 -0
  508. package/src/db/migrations/pg/meta/0010_snapshot.json +2526 -0
  509. package/src/db/migrations/pg/meta/0011_snapshot.json +2563 -0
  510. package/src/db/migrations/pg/meta/0012_snapshot.json +2573 -0
  511. package/src/db/migrations/pg/meta/0013_snapshot.json +2579 -0
  512. package/src/db/migrations/pg/meta/0014_snapshot.json +2702 -0
  513. package/src/db/migrations/pg/meta/0015_snapshot.json +2803 -0
  514. package/src/db/migrations/pg/meta/_journal.json +92 -1
  515. package/src/db/pg/__tests__/node.test.ts +38 -0
  516. package/src/db/pg/node.ts +149 -0
  517. package/src/db/pg/schema.ts +130 -12
  518. package/src/db/rehearsal-fixtures/demo-current.sql +47 -3
  519. package/src/db/schema.ts +139 -12
  520. package/src/i18n/Trans.tsx +3 -3
  521. package/src/i18n/__tests__/context.test.tsx +9 -6
  522. package/src/i18n/__tests__/detect.test.ts +65 -48
  523. package/src/i18n/__tests__/fallback.test.ts +40 -0
  524. package/src/i18n/__tests__/message-placeholders.test.ts +43 -0
  525. package/src/i18n/__tests__/middleware.test.ts +83 -0
  526. package/src/i18n/context.tsx +9 -44
  527. package/src/i18n/detect.ts +95 -28
  528. package/src/i18n/i18n.ts +41 -17
  529. package/src/i18n/index.ts +7 -5
  530. package/src/i18n/locales/{en.po → public/en.po} +298 -1151
  531. package/src/i18n/locales/public/en.ts +1 -0
  532. package/src/i18n/locales/public/zh-Hans.po +2327 -0
  533. package/src/i18n/locales/public/zh-Hans.ts +1 -0
  534. package/src/i18n/locales/public/zh-Hant.po +2327 -0
  535. package/src/i18n/locales/public/zh-Hant.ts +1 -0
  536. package/src/i18n/locales/settings/en.po +1622 -0
  537. package/src/i18n/locales/settings/en.ts +1 -0
  538. package/src/i18n/locales/settings/zh-Hans.po +1622 -0
  539. package/src/i18n/locales/settings/zh-Hans.ts +1 -0
  540. package/src/i18n/locales/settings/zh-Hant.po +1622 -0
  541. package/src/i18n/locales/settings/zh-Hant.ts +1 -0
  542. package/src/i18n/middleware.ts +37 -24
  543. package/src/index.ts +4 -5
  544. package/src/lib/__tests__/collection-groups.test.ts +80 -0
  545. package/src/lib/__tests__/constants.test.ts +1 -1
  546. package/src/lib/__tests__/csp-builder.test.ts +68 -0
  547. package/src/lib/__tests__/display-text.test.ts +27 -0
  548. package/src/lib/__tests__/embed-providers.test.ts +104 -0
  549. package/src/lib/__tests__/embed-render.test.ts +60 -0
  550. package/src/lib/__tests__/feed.test.ts +125 -63
  551. package/src/lib/__tests__/hosted-signin.test.ts +11 -2
  552. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +88 -0
  553. package/src/lib/__tests__/markdown-to-tiptap.test.ts +59 -0
  554. package/src/lib/__tests__/markdown.test.ts +47 -5
  555. package/src/lib/__tests__/navigation.test.ts +148 -0
  556. package/src/lib/__tests__/post-display.test.ts +3 -2
  557. package/src/lib/__tests__/post-meta.test.ts +3 -0
  558. package/src/lib/__tests__/resolve-config.test.ts +30 -25
  559. package/src/lib/__tests__/schemas.test.ts +35 -0
  560. package/src/lib/__tests__/slug-format.test.ts +8 -0
  561. package/src/lib/__tests__/summary.test.ts +400 -1
  562. package/src/lib/__tests__/theme.test.ts +41 -2
  563. package/src/lib/__tests__/timeline.test.ts +114 -26
  564. package/src/lib/__tests__/tiptap-render.test.ts +224 -0
  565. package/src/lib/__tests__/tiptap-to-markdown.test.ts +51 -0
  566. package/src/lib/__tests__/video-playback.test.ts +48 -0
  567. package/src/lib/__tests__/view.test.ts +115 -8
  568. package/src/lib/__tests__/worker-response-cache.test.ts +311 -0
  569. package/src/lib/__tests__/youtube.test.ts +99 -0
  570. package/src/lib/api-media.ts +55 -0
  571. package/src/lib/api-posts.ts +99 -0
  572. package/src/lib/api-search.ts +47 -0
  573. package/src/lib/api-settings.ts +122 -0
  574. package/src/lib/asset-path.ts +20 -2
  575. package/src/lib/collection-groups.ts +70 -0
  576. package/src/lib/collection-paths.ts +40 -0
  577. package/src/lib/constants.ts +3 -4
  578. package/src/lib/csp-builder.ts +99 -0
  579. package/src/lib/decorative-quote-mark.ts +11 -0
  580. package/src/lib/display-text.ts +56 -0
  581. package/src/lib/embed-providers.ts +289 -0
  582. package/src/lib/embed-render.ts +151 -0
  583. package/src/lib/env.ts +70 -1
  584. package/src/lib/excerpt.ts +11 -2
  585. package/src/lib/feed.ts +89 -66
  586. package/src/lib/footnotes.ts +146 -0
  587. package/src/lib/github-api.ts +423 -0
  588. package/src/lib/github-app-state.ts +135 -0
  589. package/src/lib/github-app.ts +487 -0
  590. package/src/lib/github-sync-queue-handler.ts +69 -0
  591. package/src/lib/github-sync-site-config.ts +57 -0
  592. package/src/lib/github-sync-trigger.ts +199 -0
  593. package/src/lib/github-sync-worker.ts +72 -0
  594. package/src/lib/hosted-signin.ts +1 -1
  595. package/src/lib/hugo-markdown.ts +255 -0
  596. package/src/lib/job-queue-cf.ts +18 -0
  597. package/src/lib/job-queue-db.ts +149 -0
  598. package/src/lib/job-queue.ts +35 -0
  599. package/src/lib/markdown-manager.ts +864 -0
  600. package/src/lib/markdown-to-tiptap.ts +4 -323
  601. package/src/lib/markdown.ts +16 -24
  602. package/src/lib/media-helpers.ts +1 -0
  603. package/src/lib/navigation.ts +40 -8
  604. package/src/lib/post-display.ts +10 -22
  605. package/src/lib/public-storage.ts +6 -1
  606. package/src/lib/render.tsx +5 -2
  607. package/src/lib/resolve-config.ts +23 -11
  608. package/src/lib/rich-image.ts +89 -0
  609. package/src/lib/schemas.ts +186 -12
  610. package/src/lib/slug-format.ts +8 -0
  611. package/src/lib/summary.ts +125 -18
  612. package/src/lib/theme.ts +119 -0
  613. package/src/lib/timeline.ts +91 -63
  614. package/src/lib/tiptap-render.ts +265 -126
  615. package/src/lib/tiptap-to-markdown.ts +6 -329
  616. package/src/lib/upload.ts +97 -6
  617. package/src/lib/version.ts +18 -0
  618. package/src/lib/video-playback.ts +73 -0
  619. package/src/lib/view.ts +130 -90
  620. package/src/lib/webhook-signature.ts +65 -0
  621. package/src/lib/worker-response-cache.ts +220 -0
  622. package/src/lib/youtube.ts +119 -0
  623. package/src/middleware/__tests__/onboarding.test.ts +3 -3
  624. package/src/middleware/__tests__/secure-headers.test.ts +28 -1
  625. package/src/middleware/config.ts +7 -10
  626. package/src/middleware/cors.ts +49 -0
  627. package/src/middleware/secure-headers.ts +45 -55
  628. package/src/node/__tests__/cli-migrate.test.ts +111 -0
  629. package/src/node/__tests__/cli-runtime-target.test.ts +1 -1
  630. package/src/node/__tests__/cli-site-token-env.test.ts +2 -2
  631. package/src/preset.css +316 -12
  632. package/src/routes/__tests__/compose.test.ts +3 -1
  633. package/src/routes/api/__tests__/collections.test.ts +81 -27
  634. package/src/routes/api/__tests__/mcp.test.ts +389 -0
  635. package/src/routes/api/__tests__/nav-items.test.ts +163 -2
  636. package/src/routes/api/__tests__/posts.test.ts +190 -0
  637. package/src/routes/api/__tests__/settings.test.ts +99 -3
  638. package/src/routes/api/__tests__/upload-multipart.test.ts +10 -2
  639. package/src/routes/api/__tests__/upload.test.ts +132 -0
  640. package/src/routes/api/__tests__/uploads.test.ts +5 -3
  641. package/src/routes/api/collections.ts +67 -26
  642. package/src/routes/api/custom-urls.ts +1 -0
  643. package/src/routes/api/export.ts +8 -4
  644. package/src/routes/api/github-sync.tsx +445 -0
  645. package/src/routes/api/internal/__tests__/sites.test.ts +45 -2
  646. package/src/routes/api/internal/sites.ts +2 -0
  647. package/src/routes/api/internal/text-attachments.ts +60 -0
  648. package/src/routes/api/internal/uploads.ts +5 -1
  649. package/src/routes/api/mcp.ts +30 -0
  650. package/src/routes/api/nav-items.ts +30 -11
  651. package/src/routes/api/palette.ts +17 -0
  652. package/src/routes/api/posts.ts +37 -106
  653. package/src/routes/api/public/__tests__/posts.test.ts +372 -0
  654. package/src/routes/api/public/posts.ts +302 -0
  655. package/src/routes/api/search.ts +1 -47
  656. package/src/routes/api/settings.ts +44 -58
  657. package/src/routes/api/upload-multipart.ts +15 -5
  658. package/src/routes/api/upload.ts +62 -49
  659. package/src/routes/api/uploads.ts +1 -0
  660. package/src/routes/auth/__tests__/hosted-sso.test.ts +33 -0
  661. package/src/routes/auth/__tests__/setup.test.ts +29 -4
  662. package/src/routes/auth/hosted-sso-expired-page.tsx +89 -0
  663. package/src/routes/auth/hosted-sso.ts +42 -1
  664. package/src/routes/auth/reset.tsx +46 -32
  665. package/src/routes/auth/setup.tsx +53 -43
  666. package/src/routes/auth/signin.tsx +34 -24
  667. package/src/routes/compose.tsx +114 -1
  668. package/src/routes/dash/__tests__/font-theme.test.ts +6 -27
  669. package/src/routes/dash/__tests__/settings-avatar.test.ts +2 -3
  670. package/src/routes/dash/custom-urls.tsx +250 -112
  671. package/src/routes/dash/settings.tsx +1163 -70
  672. package/src/routes/feed/__tests__/{rss.test.ts → feed.test.ts} +62 -123
  673. package/src/routes/feed/feed.ts +386 -0
  674. package/src/routes/feed/manifest.ts +67 -0
  675. package/src/routes/feed/sitemap.ts +11 -2
  676. package/src/routes/pages/__tests__/collection-routing.test.ts +176 -0
  677. package/src/routes/pages/__tests__/collections.test.ts +2 -3
  678. package/src/routes/pages/__tests__/featured.test.ts +2 -3
  679. package/src/routes/pages/archive.tsx +523 -127
  680. package/src/routes/pages/collection.tsx +265 -59
  681. package/src/routes/pages/collections.tsx +5 -1
  682. package/src/routes/pages/home.tsx +2 -2
  683. package/src/routes/pages/latest.tsx +3 -3
  684. package/src/routes/pages/page.tsx +221 -3
  685. package/src/routes/pages/partials.tsx +5 -0
  686. package/src/routes/pages/search.tsx +17 -1
  687. package/src/routes/pages/theme-sample.tsx +1 -2
  688. package/src/runtime/__tests__/node.test.ts +1 -1
  689. package/src/runtime/__tests__/readiness.test.ts +89 -0
  690. package/src/runtime/readiness.ts +129 -0
  691. package/src/runtime/site.ts +9 -1
  692. package/src/services/__tests__/collection.test.ts +181 -53
  693. package/src/services/__tests__/github-app-installations.test.ts +181 -0
  694. package/src/services/__tests__/github-sync-classify.test.ts +189 -0
  695. package/src/services/__tests__/github-sync-push.test.ts +159 -0
  696. package/src/services/__tests__/media.test.ts +444 -7
  697. package/src/services/__tests__/navigation.test.ts +271 -14
  698. package/src/services/__tests__/post-timeline.test.ts +85 -17
  699. package/src/services/__tests__/post.test.ts +118 -10
  700. package/src/services/__tests__/settings.test.ts +43 -21
  701. package/src/services/auth.ts +2 -2
  702. package/src/services/bootstrap.ts +10 -7
  703. package/src/services/collection.ts +644 -191
  704. package/src/services/custom-url.ts +34 -7
  705. package/src/services/export-theme/assets/client-site.css +2 -0
  706. package/src/services/export-theme/assets/client-site.js +145 -0
  707. package/src/services/export-theme/layouts/_default/alias.html +49 -0
  708. package/src/services/export-theme/layouts/_default/baseof.html +21 -0
  709. package/src/services/export-theme/layouts/_default/list.html +104 -0
  710. package/src/services/export-theme/layouts/_default/rss.xml +160 -0
  711. package/src/services/export-theme/layouts/_default/single.html +15 -0
  712. package/src/services/export-theme/layouts/archive/list.html +31 -0
  713. package/src/services/export-theme/layouts/collection/single.html +19 -0
  714. package/src/services/export-theme/layouts/collections/list.html +111 -0
  715. package/src/services/export-theme/layouts/featured/list.html +28 -0
  716. package/src/services/export-theme/layouts/index.html +41 -0
  717. package/src/services/export-theme/layouts/partials/feed-post-content.xml +107 -0
  718. package/src/services/export-theme/layouts/partials/footer.html +32 -0
  719. package/src/services/export-theme/layouts/partials/head.html +52 -0
  720. package/src/services/export-theme/layouts/partials/header.html +115 -0
  721. package/src/services/export-theme/layouts/partials/media-gallery.html +246 -0
  722. package/src/services/export-theme/layouts/partials/pagination.html +55 -0
  723. package/src/services/export-theme/layouts/partials/post-card.html +127 -0
  724. package/src/services/export-theme/layouts/partials/reply.html +91 -0
  725. package/src/services/export-theme/layouts/partials/thread-preview.html +82 -0
  726. package/src/services/export-theme/layouts/post/list.html +129 -0
  727. package/src/services/export-theme/styles/main.css +1981 -0
  728. package/src/services/export-theme/theme.toml +12 -0
  729. package/src/services/export.ts +1365 -2412
  730. package/src/services/github-app-installations.ts +302 -0
  731. package/src/services/github-sync.ts +769 -0
  732. package/src/services/index.ts +15 -0
  733. package/src/services/mcp.ts +1324 -0
  734. package/src/services/media.ts +336 -25
  735. package/src/services/navigation.ts +115 -23
  736. package/src/services/path.ts +129 -28
  737. package/src/services/post.ts +779 -207
  738. package/src/services/search.ts +3 -0
  739. package/src/services/settings.ts +29 -15
  740. package/src/services/site-admin.ts +26 -6
  741. package/src/services/upload-session.ts +69 -34
  742. package/src/style-cjk-jp.css +3 -0
  743. package/src/style-cjk-kr.css +3 -0
  744. package/src/styles/components.css +160 -59
  745. package/src/styles/fonts/et-book/et-book-bold-line-figures.woff +0 -0
  746. package/src/styles/fonts/et-book/et-book-display-italic-old-style-figures.woff +0 -0
  747. package/src/styles/fonts/et-book/et-book-roman-line-figures.woff +0 -0
  748. package/src/styles/fonts/et-book.css +31 -0
  749. package/src/styles/fonts/latin.css +1 -0
  750. package/src/styles/fonts/noto-serif-jp/400/057a6a98bda7fe57105ddaa99ec82015.woff2 +0 -0
  751. package/src/styles/fonts/noto-serif-jp/400/0acd1fe2b2ea1ad1bfee7ae1fa139d27.woff2 +0 -0
  752. package/src/styles/fonts/noto-serif-jp/400/0c2ab6b295e55f356f8020d4e7747522.woff2 +0 -0
  753. package/src/styles/fonts/noto-serif-jp/400/0c5f9492af03a4fa42c784de94649de1.woff2 +0 -0
  754. package/src/styles/fonts/noto-serif-jp/400/0d9a936885a4c39077438effd3779cbd.woff2 +0 -0
  755. package/src/styles/fonts/noto-serif-jp/400/0e645da524f7cfc0e8c3c03fb2b08428.woff2 +0 -0
  756. package/src/styles/fonts/noto-serif-jp/400/135e83b403475c5dc9e49b43501a5b84.woff2 +0 -0
  757. package/src/styles/fonts/noto-serif-jp/400/13b2c53b30e6a3e4de7132dbc18dbdfc.woff2 +0 -0
  758. package/src/styles/fonts/noto-serif-jp/400/15d95680dc31cc6ce20e0a5464106dc4.woff2 +0 -0
  759. package/src/styles/fonts/noto-serif-jp/400/1733f27476507ca68b68a803bc533cc2.woff2 +0 -0
  760. package/src/styles/fonts/noto-serif-jp/400/188c2db794f3dd7a45889ddbc81da9bb.woff2 +0 -0
  761. package/src/styles/fonts/noto-serif-jp/400/1cf737900dd49c2e88f1b3221a82a602.woff2 +0 -0
  762. package/src/styles/fonts/noto-serif-jp/400/233d3a685ee18276b319363474599d47.woff2 +0 -0
  763. package/src/styles/fonts/noto-serif-jp/400/284b53bbefb06924cf236d24c8ed5641.woff2 +0 -0
  764. package/src/styles/fonts/noto-serif-jp/400/2a56eaf19d1d38a6b57e2a388f733676.woff2 +0 -0
  765. package/src/styles/fonts/noto-serif-jp/400/2abfbab82b6a7c04426afc054d2464da.woff2 +0 -0
  766. package/src/styles/fonts/noto-serif-jp/400/2e6f4bb71ef6b38765d51acc5a79f638.woff2 +0 -0
  767. package/src/styles/fonts/noto-serif-jp/400/31194d303a67561926a544ed0e072aee.woff2 +0 -0
  768. package/src/styles/fonts/noto-serif-jp/400/3623d96698132c61e861f984ce11cc23.woff2 +0 -0
  769. package/src/styles/fonts/noto-serif-jp/400/39211b02f24c69cbd1b54f6d41b01933.woff2 +0 -0
  770. package/src/styles/fonts/noto-serif-jp/400/3e24063b19abd2b051af986a6870046d.woff2 +0 -0
  771. package/src/styles/fonts/noto-serif-jp/400/4410f876d27c9821839474b6b1059fd7.woff2 +0 -0
  772. package/src/styles/fonts/noto-serif-jp/400/479329a39a3eb6c0047e6b0981919855.woff2 +0 -0
  773. package/src/styles/fonts/noto-serif-jp/400/527ff336279465617aafbafe1a830632.woff2 +0 -0
  774. package/src/styles/fonts/noto-serif-jp/400/56b40518ea0608e62826bd44ee27f5f9.woff2 +0 -0
  775. package/src/styles/fonts/noto-serif-jp/400/5acc7f736217259db79a42bf44241c48.woff2 +0 -0
  776. package/src/styles/fonts/noto-serif-jp/400/5c05f8b75c9d7f25b3c04ca711cf43b3.woff2 +0 -0
  777. package/src/styles/fonts/noto-serif-jp/400/5e0124c7265f1b4cf0fc797b94efbebc.woff2 +0 -0
  778. package/src/styles/fonts/noto-serif-jp/400/615a7e0d63f257109d599b2a1977de68.woff2 +0 -0
  779. package/src/styles/fonts/noto-serif-jp/400/61a2c80d0c924d5a6187c02e8d1d1642.woff2 +0 -0
  780. package/src/styles/fonts/noto-serif-jp/400/62fc94f8d4c5a750b7f25e7387539910.woff2 +0 -0
  781. package/src/styles/fonts/noto-serif-jp/400/639c7c6b0b0c27c738702741cfa4b8c0.woff2 +0 -0
  782. package/src/styles/fonts/noto-serif-jp/400/68a3a4bf337f8a0722be76676e20b850.woff2 +0 -0
  783. package/src/styles/fonts/noto-serif-jp/400/6abdd163d2c4b85698db8aa1ce149361.woff2 +0 -0
  784. package/src/styles/fonts/noto-serif-jp/400/73b794d885b88f7befb7ea8ab1780a15.woff2 +0 -0
  785. package/src/styles/fonts/noto-serif-jp/400/743f290baf027d4626a86e22f3d44600.woff2 +0 -0
  786. package/src/styles/fonts/noto-serif-jp/400/757019c01c5e2c2b0f54293cea3b5636.woff2 +0 -0
  787. package/src/styles/fonts/noto-serif-jp/400/76f117a3baacda304a71965a17266a13.woff2 +0 -0
  788. package/src/styles/fonts/noto-serif-jp/400/77747f17625a259fb405b7bbdd84ac4b.woff2 +0 -0
  789. package/src/styles/fonts/noto-serif-jp/400/788498548ddfb710d6ef3c7bff79fa97.woff2 +0 -0
  790. package/src/styles/fonts/noto-serif-jp/400/78ea6c40923ce95367e3517dd1e5a849.woff2 +0 -0
  791. package/src/styles/fonts/noto-serif-jp/400/7f9a6c9286b68de9c72b0024f7beeb40.woff2 +0 -0
  792. package/src/styles/fonts/noto-serif-jp/400/823d1bb11097331238d103c7f72138dc.woff2 +0 -0
  793. package/src/styles/fonts/noto-serif-jp/400/888ba53510213d5d1f6a7deb60e569b6.woff2 +0 -0
  794. package/src/styles/fonts/noto-serif-jp/400/892aa49b2529c89bb4076d4aa51fe30f.woff2 +0 -0
  795. package/src/styles/fonts/noto-serif-jp/400/95ff41191c76ef893ac70a1043e95e44.woff2 +0 -0
  796. package/src/styles/fonts/noto-serif-jp/400/96c75862c8cec51a7c05ff025fea86cd.woff2 +0 -0
  797. package/src/styles/fonts/noto-serif-jp/400/9b21e8244f5930c48ad5073f83777b6c.woff2 +0 -0
  798. package/src/styles/fonts/noto-serif-jp/400/9fce02d1a09f464c36c9fcfb14a354c5.woff2 +0 -0
  799. package/src/styles/fonts/noto-serif-jp/400/a0a6755c7e3f9e4cf03387027dd9f16c.woff2 +0 -0
  800. package/src/styles/fonts/noto-serif-jp/400/a4475c442c6b259354c9eda62e64e7df.woff2 +0 -0
  801. package/src/styles/fonts/noto-serif-jp/400/a4a39b5f048671aa37c2b2a9581ab08a.woff2 +0 -0
  802. package/src/styles/fonts/noto-serif-jp/400/a534449d67561ac9b06489f64e57ad98.woff2 +0 -0
  803. package/src/styles/fonts/noto-serif-jp/400/a919a8c6fec6d7e8bb21177965f2e9d7.woff2 +0 -0
  804. package/src/styles/fonts/noto-serif-jp/400/ad67113f88b59582991cb0c5d33ea19f.woff2 +0 -0
  805. package/src/styles/fonts/noto-serif-jp/400/adb6e613fb99c6dd660b727101f554a8.woff2 +0 -0
  806. package/src/styles/fonts/noto-serif-jp/400/adc4a98a89870ef984ee8f4b57c8a3a5.woff2 +0 -0
  807. package/src/styles/fonts/noto-serif-jp/400/b12379ab782468d725519cd07a7d15bc.woff2 +0 -0
  808. package/src/styles/fonts/noto-serif-jp/400/b55486f8a459c838fe329d4e79a8c211.woff2 +0 -0
  809. package/src/styles/fonts/noto-serif-jp/400/bb0b15492b8cdbbec57c0bcfa0aa9241.woff2 +0 -0
  810. package/src/styles/fonts/noto-serif-jp/400/bedc74b423b7293b6ad0bdecc61c42cc.woff2 +0 -0
  811. package/src/styles/fonts/noto-serif-jp/400/bfb00d4a4c48661bd0be99f300a0faae.woff2 +0 -0
  812. package/src/styles/fonts/noto-serif-jp/400/c1b0df29ae41d764904df84e9ac83d1e.woff2 +0 -0
  813. package/src/styles/fonts/noto-serif-jp/400/c42c67070cfe99cf823df92d81c7fa6e.woff2 +0 -0
  814. package/src/styles/fonts/noto-serif-jp/400/c4d749e45ecd5a5aed5a0bb3ebfd355d.woff2 +0 -0
  815. package/src/styles/fonts/noto-serif-jp/400/c96d83978add28b356c22c4c84916733.woff2 +0 -0
  816. package/src/styles/fonts/noto-serif-jp/400/cb7ccd6494256f7a2977b7c2b0225592.woff2 +0 -0
  817. package/src/styles/fonts/noto-serif-jp/400/ce41d70ce6a069a498525c9e15c45cf2.woff2 +0 -0
  818. package/src/styles/fonts/noto-serif-jp/400/d509ee5c8241bc7c3de2039d75564fa5.woff2 +0 -0
  819. package/src/styles/fonts/noto-serif-jp/400/d675d717cd329bfd4c0524f76ae1579c.woff2 +0 -0
  820. package/src/styles/fonts/noto-serif-jp/400/dc8f6256445e68199540be9ade33529f.woff2 +0 -0
  821. package/src/styles/fonts/noto-serif-jp/400/e8c15be62faf978d208925e79ea6a10d.woff2 +0 -0
  822. package/src/styles/fonts/noto-serif-jp/400/e91c4d941ed9f5c2f3e27f205a3a225e.woff2 +0 -0
  823. package/src/styles/fonts/noto-serif-jp/400/e9c566be7a5d38a9085225f7372bc82b.woff2 +0 -0
  824. package/src/styles/fonts/noto-serif-jp/400/e9ebc567a711eeb29019ddae3e0ce7fe.woff2 +0 -0
  825. package/src/styles/fonts/noto-serif-jp/400/ecb8875a56c7b038b35432fda41ae128.woff2 +0 -0
  826. package/src/styles/fonts/noto-serif-jp/400/f1661731474b78bdf81114daf10b254f.woff2 +0 -0
  827. package/src/styles/fonts/noto-serif-jp/400/f2c58dee206ba9355046fc23d05491f7.woff2 +0 -0
  828. package/src/styles/fonts/noto-serif-jp/400/f50ac27ea4358d67fdda403c2bb52467.woff2 +0 -0
  829. package/src/styles/fonts/noto-serif-jp/400/f55334112f8e8be82d65db29887a663f.woff2 +0 -0
  830. package/src/styles/fonts/noto-serif-jp/400/f740e93f3c0277ecc616594103bca683.woff2 +0 -0
  831. package/src/styles/fonts/noto-serif-jp/400/f7633b5af033d76ff2fb3c3c266d77c5.woff2 +0 -0
  832. package/src/styles/fonts/noto-serif-jp/400/f7d8468cba2335a83ee414ea68291bab.woff2 +0 -0
  833. package/src/styles/fonts/noto-serif-jp/400/fc9ae5b600fb711f2d67e93ce768cba4.woff2 +0 -0
  834. package/src/styles/fonts/noto-serif-jp/400/fd1bceb55d3e0183ac2454b8532fec7d.woff2 +0 -0
  835. package/src/styles/fonts/noto-serif-jp/700/02611a045a7fe83a12014e3debc9f731.woff2 +0 -0
  836. package/src/styles/fonts/noto-serif-jp/700/0f5e1a18987dbc84ca05188c129e1936.woff2 +0 -0
  837. package/src/styles/fonts/noto-serif-jp/700/112743a4ab5fdd1498dfdf2b11336380.woff2 +0 -0
  838. package/src/styles/fonts/noto-serif-jp/700/1211f03d3ff5759a702631445793f60e.woff2 +0 -0
  839. package/src/styles/fonts/noto-serif-jp/700/147b24c2f08d03bbed30887d4cb3311c.woff2 +0 -0
  840. package/src/styles/fonts/noto-serif-jp/700/16663e567f81f4725a1522f37e18f71f.woff2 +0 -0
  841. package/src/styles/fonts/noto-serif-jp/700/1824321da801e6257b902f3d1d09054c.woff2 +0 -0
  842. package/src/styles/fonts/noto-serif-jp/700/1a3a92c7c060a71a6c35819b9ebcdbb9.woff2 +0 -0
  843. package/src/styles/fonts/noto-serif-jp/700/1fba7ec0e412e911bf31841de5a8a4e4.woff2 +0 -0
  844. package/src/styles/fonts/noto-serif-jp/700/28a97af9ab9d38983d20f39ff09840e1.woff2 +0 -0
  845. package/src/styles/fonts/noto-serif-jp/700/2bfaadaf3479c72286248e6de0be0ec9.woff2 +0 -0
  846. package/src/styles/fonts/noto-serif-jp/700/2c8c55e4cec85ce77e95cac9d330f5cf.woff2 +0 -0
  847. package/src/styles/fonts/noto-serif-jp/700/2ed9981d2e8983365bd051159b976b6d.woff2 +0 -0
  848. package/src/styles/fonts/noto-serif-jp/700/2f4c633e923ba30c6ba376367379fc91.woff2 +0 -0
  849. package/src/styles/fonts/noto-serif-jp/700/349965eee0a9b6c984a319ab96a4ece9.woff2 +0 -0
  850. package/src/styles/fonts/noto-serif-jp/700/36cda3eae13370b934bba4429fab9078.woff2 +0 -0
  851. package/src/styles/fonts/noto-serif-jp/700/3aa13f1843d7c8fa16109b6395a9a29d.woff2 +0 -0
  852. package/src/styles/fonts/noto-serif-jp/700/3e2c06bdd9dcab29aeaeb0cfb7fa608a.woff2 +0 -0
  853. package/src/styles/fonts/noto-serif-jp/700/424123f9371ba357087363263eeafcda.woff2 +0 -0
  854. package/src/styles/fonts/noto-serif-jp/700/43b3bbf43055c3e65db9cdf2c5b18c1c.woff2 +0 -0
  855. package/src/styles/fonts/noto-serif-jp/700/44704471b60c18b3ecec5300a88f1102.woff2 +0 -0
  856. package/src/styles/fonts/noto-serif-jp/700/46604090efbb99c2db1800928af0a239.woff2 +0 -0
  857. package/src/styles/fonts/noto-serif-jp/700/46998df85d31e629c0142f0556f5e7c5.woff2 +0 -0
  858. package/src/styles/fonts/noto-serif-jp/700/49902f1c41b281f8eb3771abdd4dccb8.woff2 +0 -0
  859. package/src/styles/fonts/noto-serif-jp/700/4db4f31a16965baa42cb7dad667a8f04.woff2 +0 -0
  860. package/src/styles/fonts/noto-serif-jp/700/572b9e28f1e6d89b765a16d809db84a1.woff2 +0 -0
  861. package/src/styles/fonts/noto-serif-jp/700/5921cef36750ff6092d94b83a802823b.woff2 +0 -0
  862. package/src/styles/fonts/noto-serif-jp/700/59e95d8b5332dcdae894a19946ab767d.woff2 +0 -0
  863. package/src/styles/fonts/noto-serif-jp/700/5ec349ff62b9ee5fe0c6dae867704dc4.woff2 +0 -0
  864. package/src/styles/fonts/noto-serif-jp/700/601f812e8b1eafecd5ba585a6a6d7962.woff2 +0 -0
  865. package/src/styles/fonts/noto-serif-jp/700/6025ddaf99d86cc8bfd781006b19fbd6.woff2 +0 -0
  866. package/src/styles/fonts/noto-serif-jp/700/60f6e28b3f0400b0e8fe32c3c7116102.woff2 +0 -0
  867. package/src/styles/fonts/noto-serif-jp/700/64a404d675f1d726f0891b7a80794595.woff2 +0 -0
  868. package/src/styles/fonts/noto-serif-jp/700/7158022889c6c177062ac85036e7af10.woff2 +0 -0
  869. package/src/styles/fonts/noto-serif-jp/700/7e38ca789a9e76ac91d9256374e154f0.woff2 +0 -0
  870. package/src/styles/fonts/noto-serif-jp/700/815a0b797465190f60d8b1a04656c42e.woff2 +0 -0
  871. package/src/styles/fonts/noto-serif-jp/700/8726558cf297cbda831cc19514897205.woff2 +0 -0
  872. package/src/styles/fonts/noto-serif-jp/700/8855472f3c02f6c7ebb3216617ebe4af.woff2 +0 -0
  873. package/src/styles/fonts/noto-serif-jp/700/8e231d707f0c4f8e3cde90a6b52a79aa.woff2 +0 -0
  874. package/src/styles/fonts/noto-serif-jp/700/9064c12fd72c7aba235d8c3881755b91.woff2 +0 -0
  875. package/src/styles/fonts/noto-serif-jp/700/911b9e53e9814de2998c60bf3115f560.woff2 +0 -0
  876. package/src/styles/fonts/noto-serif-jp/700/92fdc376bce277874e75db666f175b44.woff2 +0 -0
  877. package/src/styles/fonts/noto-serif-jp/700/930a23430f0eb64480d7fe5f82834e21.woff2 +0 -0
  878. package/src/styles/fonts/noto-serif-jp/700/93bed0e5f2dd5a21cf73304fcfbc149d.woff2 +0 -0
  879. package/src/styles/fonts/noto-serif-jp/700/93f3ea6918533d96d9c252378b9a4af0.woff2 +0 -0
  880. package/src/styles/fonts/noto-serif-jp/700/95a0951d2a2722ff773a1a45e8c474d6.woff2 +0 -0
  881. package/src/styles/fonts/noto-serif-jp/700/982cd1765ca6bbb3e6547e47834d5148.woff2 +0 -0
  882. package/src/styles/fonts/noto-serif-jp/700/9b0c3caac458c7bc9c9133e40405ddea.woff2 +0 -0
  883. package/src/styles/fonts/noto-serif-jp/700/9f3bdab4025dc7c5ba1a5871e62e8918.woff2 +0 -0
  884. package/src/styles/fonts/noto-serif-jp/700/a00e94d4f04df6aa659db9c4954c7efe.woff2 +0 -0
  885. package/src/styles/fonts/noto-serif-jp/700/a2b0597837e382aca19919c4b74e58c8.woff2 +0 -0
  886. package/src/styles/fonts/noto-serif-jp/700/a373e85c96aa0dc303092429fb837e51.woff2 +0 -0
  887. package/src/styles/fonts/noto-serif-jp/700/a6e044424780a57610833cc856c15bf5.woff2 +0 -0
  888. package/src/styles/fonts/noto-serif-jp/700/a7c58070d68dc1724e9d4e781afb78db.woff2 +0 -0
  889. package/src/styles/fonts/noto-serif-jp/700/accc43762ad05edfbff66ba1380d192a.woff2 +0 -0
  890. package/src/styles/fonts/noto-serif-jp/700/ad47bcd6d4663026d648c132d969318d.woff2 +0 -0
  891. package/src/styles/fonts/noto-serif-jp/700/af57a2e8c72f9ba0efb1af771d32c124.woff2 +0 -0
  892. package/src/styles/fonts/noto-serif-jp/700/b0ae5f374e43dac992a4a5d334cebc0b.woff2 +0 -0
  893. package/src/styles/fonts/noto-serif-jp/700/b91234c10fd8b8c8abc88e03afe66a1f.woff2 +0 -0
  894. package/src/styles/fonts/noto-serif-jp/700/b9317198f118a1dfd8ddf2b82ec028f3.woff2 +0 -0
  895. package/src/styles/fonts/noto-serif-jp/700/ba825ae79b1a6f7e0cce5215fcb5c96f.woff2 +0 -0
  896. package/src/styles/fonts/noto-serif-jp/700/bc6183ac08f0fac78c46f80c10cf7c92.woff2 +0 -0
  897. package/src/styles/fonts/noto-serif-jp/700/bc8ccea5abf6598cf3cfa97eb59804bb.woff2 +0 -0
  898. package/src/styles/fonts/noto-serif-jp/700/bc95a792cbca6639214c9b0da13392ff.woff2 +0 -0
  899. package/src/styles/fonts/noto-serif-jp/700/bcfa62f35731856246c146d3a6932bf3.woff2 +0 -0
  900. package/src/styles/fonts/noto-serif-jp/700/c063897793f593eb26d6ff7b7baaba18.woff2 +0 -0
  901. package/src/styles/fonts/noto-serif-jp/700/c6c2971ad1f3221f6cf84028aa0f477e.woff2 +0 -0
  902. package/src/styles/fonts/noto-serif-jp/700/c97f41eef722121d86f55d553c056a39.woff2 +0 -0
  903. package/src/styles/fonts/noto-serif-jp/700/ca62704509932d3232d62918de97af3f.woff2 +0 -0
  904. package/src/styles/fonts/noto-serif-jp/700/d1f064825fa5784b5c930652bd831cce.woff2 +0 -0
  905. package/src/styles/fonts/noto-serif-jp/700/d2faabcedd19f016e7b21bce073c0ec8.woff2 +0 -0
  906. package/src/styles/fonts/noto-serif-jp/700/d320171e57480510f87dbbc7d5264b0a.woff2 +0 -0
  907. package/src/styles/fonts/noto-serif-jp/700/d3a72a99d365dddfbca8d017a8011368.woff2 +0 -0
  908. package/src/styles/fonts/noto-serif-jp/700/d42aafa0f246ad3b4c16fe96d3a3a432.woff2 +0 -0
  909. package/src/styles/fonts/noto-serif-jp/700/d4aaf23c13ae808a4bed617afd13aa07.woff2 +0 -0
  910. package/src/styles/fonts/noto-serif-jp/700/d6bb686bddfbe8f38a36a68e609a8667.woff2 +0 -0
  911. package/src/styles/fonts/noto-serif-jp/700/d9020ff69a83b2a6ee1f42ae480f7db0.woff2 +0 -0
  912. package/src/styles/fonts/noto-serif-jp/700/da90a9012ab2d98f759e3fa0820ef502.woff2 +0 -0
  913. package/src/styles/fonts/noto-serif-jp/700/df10d94bea357a43313e20da5d84bd30.woff2 +0 -0
  914. package/src/styles/fonts/noto-serif-jp/700/e34b2b141e472dc776c86fdf8eea23b0.woff2 +0 -0
  915. package/src/styles/fonts/noto-serif-jp/700/e444fd88f1390636e603d0d681538ac8.woff2 +0 -0
  916. package/src/styles/fonts/noto-serif-jp/700/e79898628283edc27180fc39d0d769c1.woff2 +0 -0
  917. package/src/styles/fonts/noto-serif-jp/700/eaf332445a40942928e5f5750c1c3116.woff2 +0 -0
  918. package/src/styles/fonts/noto-serif-jp/700/f91cd722855f4269256eae1187df64ec.woff2 +0 -0
  919. package/src/styles/fonts/noto-serif-jp/700/fc895f5ce66b656f4a933097bf2a8775.woff2 +0 -0
  920. package/src/styles/fonts/noto-serif-jp/700/fca6720fd14c467d29a90f18ef3859b9.woff2 +0 -0
  921. package/src/styles/fonts/noto-serif-jp/700/fd1bb507bcbf04856eeb7f7fd47ea579.woff2 +0 -0
  922. package/src/styles/fonts/noto-serif-jp/noto-serif-jp.css +3349 -0
  923. package/src/styles/fonts/noto-serif-kr/400/033466ef683afe931f7f520cfb42d928.woff2 +0 -0
  924. package/src/styles/fonts/noto-serif-kr/400/05da12edb9d52210581dc6ec4541031f.woff2 +0 -0
  925. package/src/styles/fonts/noto-serif-kr/400/0968e4861204b51f62a2f8e9f15dd5e0.woff2 +0 -0
  926. package/src/styles/fonts/noto-serif-kr/400/0d5dec931dc885f07fe5cd5af8bed675.woff2 +0 -0
  927. package/src/styles/fonts/noto-serif-kr/400/1076f0f6f66d28d7a2f16427faad4413.woff2 +0 -0
  928. package/src/styles/fonts/noto-serif-kr/400/12ef1ba76bd20b004b865266a1aa70b3.woff2 +0 -0
  929. package/src/styles/fonts/noto-serif-kr/400/152395634a207579552f8cb54db88599.woff2 +0 -0
  930. package/src/styles/fonts/noto-serif-kr/400/1a08931435f885e707edb85978ea3bb6.woff2 +0 -0
  931. package/src/styles/fonts/noto-serif-kr/400/31a197213ae61d7043c81013f52d10d6.woff2 +0 -0
  932. package/src/styles/fonts/noto-serif-kr/400/37dbd564820cce2f7b3331e442da0df3.woff2 +0 -0
  933. package/src/styles/fonts/noto-serif-kr/400/37dc8505b20b37a2477a9121b16d44a7.woff2 +0 -0
  934. package/src/styles/fonts/noto-serif-kr/400/3ac0a07878cacdc98bb889f40f5970d3.woff2 +0 -0
  935. package/src/styles/fonts/noto-serif-kr/400/491bbba374093fff045f55803a22bfef.woff2 +0 -0
  936. package/src/styles/fonts/noto-serif-kr/400/4d38e56738156625329d93bd5c8cc74a.woff2 +0 -0
  937. package/src/styles/fonts/noto-serif-kr/400/57e9a86651366c1ba299e47aa7e358c2.woff2 +0 -0
  938. package/src/styles/fonts/noto-serif-kr/400/57f89adeae55aa7fe2add3fc1801ce03.woff2 +0 -0
  939. package/src/styles/fonts/noto-serif-kr/400/5df9ce98c75f8c50fb4fff7f01a23925.woff2 +0 -0
  940. package/src/styles/fonts/noto-serif-kr/400/60020a830d809c26970cf8e48ba1eeda.woff2 +0 -0
  941. package/src/styles/fonts/noto-serif-kr/400/60bcfa3ea7446eb42394aab02d28be40.woff2 +0 -0
  942. package/src/styles/fonts/noto-serif-kr/400/63fafcb069520613d0ea35ad3e6b1e42.woff2 +0 -0
  943. package/src/styles/fonts/noto-serif-kr/400/65a60d87c64228d258a123cbe85f5f31.woff2 +0 -0
  944. package/src/styles/fonts/noto-serif-kr/400/6968e889e18891d912809fe484c2e745.woff2 +0 -0
  945. package/src/styles/fonts/noto-serif-kr/400/70714891ad3fbfc3d5f10a8669dacc5a.woff2 +0 -0
  946. package/src/styles/fonts/noto-serif-kr/400/761d84afca8d7f34eebefe538adba827.woff2 +0 -0
  947. package/src/styles/fonts/noto-serif-kr/400/76e07530323418ec723c5d7634c9bcca.woff2 +0 -0
  948. package/src/styles/fonts/noto-serif-kr/400/773819b7b9b8fd404a929867c0fd677e.woff2 +0 -0
  949. package/src/styles/fonts/noto-serif-kr/400/7817dd16805145d8538ad57590f69f5a.woff2 +0 -0
  950. package/src/styles/fonts/noto-serif-kr/400/7b59f0ec7792b18458dc5a361e37884c.woff2 +0 -0
  951. package/src/styles/fonts/noto-serif-kr/400/7c3945788a689a69356c1a622d69d48b.woff2 +0 -0
  952. package/src/styles/fonts/noto-serif-kr/400/835b6505bb9eea9678925a1fa885353d.woff2 +0 -0
  953. package/src/styles/fonts/noto-serif-kr/400/8b5c9ef81159f31d2ab35f45ca4373e0.woff2 +0 -0
  954. package/src/styles/fonts/noto-serif-kr/400/8b91c7c2ed390f1278b9befa3aa87233.woff2 +0 -0
  955. package/src/styles/fonts/noto-serif-kr/400/8fb45117a62d92dce44a80f0b729ead5.woff2 +0 -0
  956. package/src/styles/fonts/noto-serif-kr/400/90b7848e9b1623b77bdcf155e90b839c.woff2 +0 -0
  957. package/src/styles/fonts/noto-serif-kr/400/985d4d41afa0934a4eb2de35473fb9a2.woff2 +0 -0
  958. package/src/styles/fonts/noto-serif-kr/400/9bb38fd05201de666b88b82d56a386f7.woff2 +0 -0
  959. package/src/styles/fonts/noto-serif-kr/400/9eff2ab2a9d2ced47ab761bf6fdb11ec.woff2 +0 -0
  960. package/src/styles/fonts/noto-serif-kr/400/9f1963fe8d4d6878d717d872a8f3fdb5.woff2 +0 -0
  961. package/src/styles/fonts/noto-serif-kr/400/a0d00fe4816c95a8c7dffd445ef00b03.woff2 +0 -0
  962. package/src/styles/fonts/noto-serif-kr/400/a0f199077fa1a33bc2a1e01c64de7fe6.woff2 +0 -0
  963. package/src/styles/fonts/noto-serif-kr/400/a7c35e42a659347e490c3cb7983b2e7f.woff2 +0 -0
  964. package/src/styles/fonts/noto-serif-kr/400/aa662092cf249707123ca4d575e2764b.woff2 +0 -0
  965. package/src/styles/fonts/noto-serif-kr/400/aae1eda193285ab817a2d1b408440326.woff2 +0 -0
  966. package/src/styles/fonts/noto-serif-kr/400/b0cb664cb2e1371efda8943c0b7dcd1c.woff2 +0 -0
  967. package/src/styles/fonts/noto-serif-kr/400/b0d1cdced482352cf0d3ae58638aacb9.woff2 +0 -0
  968. package/src/styles/fonts/noto-serif-kr/400/b320f5d185b2cff933ac549c184031c5.woff2 +0 -0
  969. package/src/styles/fonts/noto-serif-kr/400/ba9c59d7dfa4494db1bb764ada81467d.woff2 +0 -0
  970. package/src/styles/fonts/noto-serif-kr/400/bab547459d514f46206e340c4bb2dc88.woff2 +0 -0
  971. package/src/styles/fonts/noto-serif-kr/400/bcc39eda837bb7a7a3d37c8c60fffb81.woff2 +0 -0
  972. package/src/styles/fonts/noto-serif-kr/400/bd73264d7f98776708d5d6f3c9b78fcc.woff2 +0 -0
  973. package/src/styles/fonts/noto-serif-kr/400/bed610b217d500f5975cfc9fe6157570.woff2 +0 -0
  974. package/src/styles/fonts/noto-serif-kr/400/bf8901f8f11d4f433ea17dabc9370ea6.woff2 +0 -0
  975. package/src/styles/fonts/noto-serif-kr/400/c4143bb9f2fe77b6ccf20088a8904650.woff2 +0 -0
  976. package/src/styles/fonts/noto-serif-kr/400/c7ff3f6bbdcd5f604b7343602ab904df.woff2 +0 -0
  977. package/src/styles/fonts/noto-serif-kr/400/c945c62368357d05a53206620460fb30.woff2 +0 -0
  978. package/src/styles/fonts/noto-serif-kr/400/ce022e18a1377ac509443c3c3790b431.woff2 +0 -0
  979. package/src/styles/fonts/noto-serif-kr/400/cf9cffe56636322f62b40d61130fbc5e.woff2 +0 -0
  980. package/src/styles/fonts/noto-serif-kr/400/d20d8944bc0b85f5b2aae4b24f343516.woff2 +0 -0
  981. package/src/styles/fonts/noto-serif-kr/400/d36644d6502527e1fff205d0c7eca434.woff2 +0 -0
  982. package/src/styles/fonts/noto-serif-kr/400/d40ab99c7a38026f411c8f112f742b48.woff2 +0 -0
  983. package/src/styles/fonts/noto-serif-kr/400/dc6e234ded795e91f76d6647f628fbf0.woff2 +0 -0
  984. package/src/styles/fonts/noto-serif-kr/400/dd0cfdfdac0866e66d587e2b5a9e9961.woff2 +0 -0
  985. package/src/styles/fonts/noto-serif-kr/400/de304c8a02e45ded4f8dcc479d167198.woff2 +0 -0
  986. package/src/styles/fonts/noto-serif-kr/400/e8c364c16daa04835bf32d293d2598db.woff2 +0 -0
  987. package/src/styles/fonts/noto-serif-kr/400/ec86e23683052da5cfdc3b77641bd15a.woff2 +0 -0
  988. package/src/styles/fonts/noto-serif-kr/400/eda1b0cb6d1719dd9bedcf3216a9e8de.woff2 +0 -0
  989. package/src/styles/fonts/noto-serif-kr/400/ee6fce20b420a480714607c66d7f97e5.woff2 +0 -0
  990. package/src/styles/fonts/noto-serif-kr/400/eee3836d6ac17ebb2c450bbcbc9db121.woff2 +0 -0
  991. package/src/styles/fonts/noto-serif-kr/400/f5738255e92d8dd34a46d1bcdf4c4074.woff2 +0 -0
  992. package/src/styles/fonts/noto-serif-kr/400/fa6e58ce4b52695e7ae19bbea6336ec8.woff2 +0 -0
  993. package/src/styles/fonts/noto-serif-kr/400/fd2069e6e8588c70b4f11364093b81f2.woff2 +0 -0
  994. package/src/styles/fonts/noto-serif-kr/700/04b8bde59cff68eeee74fb1914eb09c7.woff2 +0 -0
  995. package/src/styles/fonts/noto-serif-kr/700/05ac821472e235943ed1435e4bb8ecad.woff2 +0 -0
  996. package/src/styles/fonts/noto-serif-kr/700/074c1f8483d5a4d8c45c8c5f3e4cbb32.woff2 +0 -0
  997. package/src/styles/fonts/noto-serif-kr/700/1380210a722ac9aeef54005ad7b015c9.woff2 +0 -0
  998. package/src/styles/fonts/noto-serif-kr/700/17c7f5d0a45e92ede0e5dec3a2c77efa.woff2 +0 -0
  999. package/src/styles/fonts/noto-serif-kr/700/1be602ad456b0d75902d352116cb35fd.woff2 +0 -0
  1000. package/src/styles/fonts/noto-serif-kr/700/2612c60f9dc5459ac42763591240a1d6.woff2 +0 -0
  1001. package/src/styles/fonts/noto-serif-kr/700/2ab8cf1f23a5ac7939a7876054e711a7.woff2 +0 -0
  1002. package/src/styles/fonts/noto-serif-kr/700/34e6750e00c3a911ef87dc66b336594d.woff2 +0 -0
  1003. package/src/styles/fonts/noto-serif-kr/700/36f3df4730cfca4fc77fe52b52e6a126.woff2 +0 -0
  1004. package/src/styles/fonts/noto-serif-kr/700/37887deb7a9c466cf2af6ee7ff372ea6.woff2 +0 -0
  1005. package/src/styles/fonts/noto-serif-kr/700/3aba89c4d4281553078de4622b498cbb.woff2 +0 -0
  1006. package/src/styles/fonts/noto-serif-kr/700/3d88558097559d775231194d43745ba7.woff2 +0 -0
  1007. package/src/styles/fonts/noto-serif-kr/700/3dc3e8c35524cad2d1236f3393104ccf.woff2 +0 -0
  1008. package/src/styles/fonts/noto-serif-kr/700/45140742dfa8686b58db2899ce89f89a.woff2 +0 -0
  1009. package/src/styles/fonts/noto-serif-kr/700/469d2754e315e77270c12fa0ab28026c.woff2 +0 -0
  1010. package/src/styles/fonts/noto-serif-kr/700/4c0760284569b87d8f2290c324953973.woff2 +0 -0
  1011. package/src/styles/fonts/noto-serif-kr/700/4cba92c942694bdc479bc54101bb1aa4.woff2 +0 -0
  1012. package/src/styles/fonts/noto-serif-kr/700/4e7d263c30bb48604069a3b4404d4eeb.woff2 +0 -0
  1013. package/src/styles/fonts/noto-serif-kr/700/5279c7e465d9871edf51fd02e691eee7.woff2 +0 -0
  1014. package/src/styles/fonts/noto-serif-kr/700/54bfdb6f21f9d6191e5100580239e14f.woff2 +0 -0
  1015. package/src/styles/fonts/noto-serif-kr/700/56be5e32fd01668fe5ba814d94905839.woff2 +0 -0
  1016. package/src/styles/fonts/noto-serif-kr/700/615b38e0b5bb3ea68426f44f47706e6f.woff2 +0 -0
  1017. package/src/styles/fonts/noto-serif-kr/700/65e9c4585d71bf48a5c62f367010da16.woff2 +0 -0
  1018. package/src/styles/fonts/noto-serif-kr/700/6b75213eb0be40ce84241eb2bb438a2e.woff2 +0 -0
  1019. package/src/styles/fonts/noto-serif-kr/700/7520ca6ca8c60eb1e62d50e71c8d30f1.woff2 +0 -0
  1020. package/src/styles/fonts/noto-serif-kr/700/759a647b77791b1e98f99bc0ab5317a7.woff2 +0 -0
  1021. package/src/styles/fonts/noto-serif-kr/700/76e51630143b95b6322ef93ad330da7a.woff2 +0 -0
  1022. package/src/styles/fonts/noto-serif-kr/700/79aa7c8c842c4a27cf57c0a3a1ffca5a.woff2 +0 -0
  1023. package/src/styles/fonts/noto-serif-kr/700/7cc72fd2c9105560422b6a67c6162945.woff2 +0 -0
  1024. package/src/styles/fonts/noto-serif-kr/700/7d0ea0690e432462f4d05a23d720ec19.woff2 +0 -0
  1025. package/src/styles/fonts/noto-serif-kr/700/7dffa5c1bec57e0903fd62357401ff1a.woff2 +0 -0
  1026. package/src/styles/fonts/noto-serif-kr/700/82f6ccc063960eed1cdfd1d61ada8862.woff2 +0 -0
  1027. package/src/styles/fonts/noto-serif-kr/700/845b4b67564d62cf5cad242f7ac08ea5.woff2 +0 -0
  1028. package/src/styles/fonts/noto-serif-kr/700/84c8e7bc0931008ed91f44ed12dfea94.woff2 +0 -0
  1029. package/src/styles/fonts/noto-serif-kr/700/887a11290ba78b1e66c6d2f67043005e.woff2 +0 -0
  1030. package/src/styles/fonts/noto-serif-kr/700/88db1d042074fb6e66821ffc10941930.woff2 +0 -0
  1031. package/src/styles/fonts/noto-serif-kr/700/8dc18c0cebe6aa4bf4c45dbb831048ab.woff2 +0 -0
  1032. package/src/styles/fonts/noto-serif-kr/700/919a879bd2d580d8491a31a449390689.woff2 +0 -0
  1033. package/src/styles/fonts/noto-serif-kr/700/91da6cb174bebfb96976e2f1316be2fe.woff2 +0 -0
  1034. package/src/styles/fonts/noto-serif-kr/700/934dfdbed2bb2c4b6129199d699a34fa.woff2 +0 -0
  1035. package/src/styles/fonts/noto-serif-kr/700/97d7a17234f2b5030ae9697ae00aded2.woff2 +0 -0
  1036. package/src/styles/fonts/noto-serif-kr/700/995c4fda62d25c3b79dd5987737df6ae.woff2 +0 -0
  1037. package/src/styles/fonts/noto-serif-kr/700/9d0afa53dc2f92ba42024f55f11f6779.woff2 +0 -0
  1038. package/src/styles/fonts/noto-serif-kr/700/9e13a47242926f1cd7d68c08d9ef8889.woff2 +0 -0
  1039. package/src/styles/fonts/noto-serif-kr/700/a379ada89abd750c587ded29f77e731c.woff2 +0 -0
  1040. package/src/styles/fonts/noto-serif-kr/700/a48be56ce5d3a99dc8f8331398004412.woff2 +0 -0
  1041. package/src/styles/fonts/noto-serif-kr/700/a806759e72987951d6d08b7cbdf36a1b.woff2 +0 -0
  1042. package/src/styles/fonts/noto-serif-kr/700/a88481ec8bc7be11cb66e46781a79bb9.woff2 +0 -0
  1043. package/src/styles/fonts/noto-serif-kr/700/a9a016d4a93409f65278327e8a8bb38d.woff2 +0 -0
  1044. package/src/styles/fonts/noto-serif-kr/700/ab92d41062a7644faa45f50bf384454a.woff2 +0 -0
  1045. package/src/styles/fonts/noto-serif-kr/700/b326a8a9c33b554db570da94f60bc380.woff2 +0 -0
  1046. package/src/styles/fonts/noto-serif-kr/700/b6117ff2993b11bb1fdc7ea3588a010c.woff2 +0 -0
  1047. package/src/styles/fonts/noto-serif-kr/700/b61982951bd51b724143c30dfaaa9fe9.woff2 +0 -0
  1048. package/src/styles/fonts/noto-serif-kr/700/c0098958e20db68cab90097b5e62516f.woff2 +0 -0
  1049. package/src/styles/fonts/noto-serif-kr/700/c4bfaa5e50798246e3770718b7a7c84a.woff2 +0 -0
  1050. package/src/styles/fonts/noto-serif-kr/700/c84598999133455503042e06f4ab79cb.woff2 +0 -0
  1051. package/src/styles/fonts/noto-serif-kr/700/ca9a533988d7019597a60d4e17127e0c.woff2 +0 -0
  1052. package/src/styles/fonts/noto-serif-kr/700/cf2b28f90f47276f7e2688a65e88a101.woff2 +0 -0
  1053. package/src/styles/fonts/noto-serif-kr/700/d689b1861d7e4377dd72ad3013482612.woff2 +0 -0
  1054. package/src/styles/fonts/noto-serif-kr/700/d9047070d72a816b3dba9d40c2d85e69.woff2 +0 -0
  1055. package/src/styles/fonts/noto-serif-kr/700/da04549f3f4ed28076b01b8cd710d313.woff2 +0 -0
  1056. package/src/styles/fonts/noto-serif-kr/700/da7af303f8c645f9a9dbae0e6e32dd35.woff2 +0 -0
  1057. package/src/styles/fonts/noto-serif-kr/700/df9568257eb29b156449fdd4bec5ec76.woff2 +0 -0
  1058. package/src/styles/fonts/noto-serif-kr/700/e067cd0ed76c90cd0a93c9339253f20b.woff2 +0 -0
  1059. package/src/styles/fonts/noto-serif-kr/700/e08b07772e7bed3cec2832d43f7fd339.woff2 +0 -0
  1060. package/src/styles/fonts/noto-serif-kr/700/e12150d5a39b30be8f567968c7a527b0.woff2 +0 -0
  1061. package/src/styles/fonts/noto-serif-kr/700/e53fcb2381eee345db4f6f973dd95a3e.woff2 +0 -0
  1062. package/src/styles/fonts/noto-serif-kr/700/e5bd313ef81f687d398aacb11cec3069.woff2 +0 -0
  1063. package/src/styles/fonts/noto-serif-kr/700/eb5afb3d952b8593782caec6026514b6.woff2 +0 -0
  1064. package/src/styles/fonts/noto-serif-kr/700/edd6a4f608d04fc0351d7688cfc321e4.woff2 +0 -0
  1065. package/src/styles/fonts/noto-serif-kr/700/f9695c6c4df2bf6bc03045ff79d4f01f.woff2 +0 -0
  1066. package/src/styles/fonts/noto-serif-kr/700/fada6eaa68ff8816afe43d2a36c5423e.woff2 +0 -0
  1067. package/src/styles/fonts/noto-serif-kr/700/fbcc4bf5367218951172bdee6f77d7a6.woff2 +0 -0
  1068. package/src/styles/fonts/noto-serif-kr/noto-serif-kr.css +2601 -0
  1069. package/src/styles/site-media.css +823 -0
  1070. package/src/styles/tokens.css +292 -21
  1071. package/src/styles/ui.css +4217 -1869
  1072. package/src/types/bindings.ts +3 -1
  1073. package/src/types/config.ts +139 -9
  1074. package/src/types/constants.ts +46 -11
  1075. package/src/types/entities.ts +24 -8
  1076. package/src/types/operations.ts +80 -1
  1077. package/src/types/props.ts +12 -3
  1078. package/src/types/raw-assets.d.ts +39 -0
  1079. package/src/types/views.ts +28 -7
  1080. package/src/ui/__tests__/color-themes.test.ts +7 -40
  1081. package/src/ui/__tests__/font-themes.test.ts +30 -11
  1082. package/src/ui/color-themes.ts +311 -270
  1083. package/src/ui/compose/ComposeDialog.tsx +712 -394
  1084. package/src/ui/compose/ComposePrompt.tsx +15 -10
  1085. package/src/ui/dash/ActionButtons.tsx +27 -18
  1086. package/src/ui/dash/DangerZone.tsx +15 -10
  1087. package/src/ui/dash/FormatBadge.tsx +21 -8
  1088. package/src/ui/dash/StatusBadge.tsx +33 -22
  1089. package/src/ui/dash/appearance/AdvancedContent.tsx +44 -30
  1090. package/src/ui/dash/appearance/CodeInjectionContent.tsx +164 -0
  1091. package/src/ui/dash/appearance/ColorThemeContent.tsx +127 -160
  1092. package/src/ui/dash/appearance/FontThemeContent.tsx +20 -15
  1093. package/src/ui/dash/appearance/NavigationContent.tsx +368 -225
  1094. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +54 -0
  1095. package/src/ui/dash/settings/AccountContent.tsx +34 -23
  1096. package/src/ui/dash/settings/AccountMenuContent.tsx +226 -137
  1097. package/src/ui/dash/settings/ApiTokensContent.tsx +122 -79
  1098. package/src/ui/dash/settings/AvatarContent.tsx +73 -45
  1099. package/src/ui/dash/settings/DeleteAccountContent.tsx +106 -66
  1100. package/src/ui/dash/settings/GeneralContent.tsx +274 -157
  1101. package/src/ui/dash/settings/GitHubSyncContent.tsx +558 -0
  1102. package/src/ui/dash/settings/SessionsContent.tsx +79 -38
  1103. package/src/ui/dash/settings/SettingsRootContent.tsx +209 -109
  1104. package/src/ui/dash/settings/__tests__/AccountMenuContent.test.tsx +67 -0
  1105. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +75 -0
  1106. package/src/ui/feed/CuratedThreadPreview.tsx +24 -14
  1107. package/src/ui/feed/LinkCard.tsx +116 -84
  1108. package/src/ui/feed/LinkPreview.tsx +75 -0
  1109. package/src/ui/feed/NoteCard.tsx +60 -16
  1110. package/src/ui/feed/PostStatusBadges.tsx +15 -0
  1111. package/src/ui/feed/QuoteCard.tsx +10 -3
  1112. package/src/ui/feed/ThreadPreview.tsx +62 -36
  1113. package/src/ui/feed/TimelineFeed.tsx +2 -1
  1114. package/src/ui/feed/__tests__/thread-preview.test.ts +220 -11
  1115. package/src/ui/feed/__tests__/timeline-cards.test.ts +186 -5
  1116. package/src/ui/feed/thread-preview-state.ts +23 -10
  1117. package/src/ui/font-themes.ts +57 -97
  1118. package/src/ui/layouts/BaseLayout.tsx +221 -136
  1119. package/src/ui/layouts/SiteLayout.tsx +431 -143
  1120. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +76 -6
  1121. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +76 -0
  1122. package/src/ui/pages/ArchivePage.tsx +320 -207
  1123. package/src/ui/pages/BrandPage.tsx +499 -341
  1124. package/src/ui/pages/CollectionEditorPage.tsx +50 -30
  1125. package/src/ui/pages/CollectionPage.tsx +201 -61
  1126. package/src/ui/pages/CollectionsPage.tsx +27 -18
  1127. package/src/ui/pages/ComposePage.tsx +15 -10
  1128. package/src/ui/pages/FeaturedPage.tsx +16 -11
  1129. package/src/ui/pages/HomePage.tsx +15 -10
  1130. package/src/ui/pages/PostPage.tsx +5 -1
  1131. package/src/ui/pages/SearchPage.tsx +93 -43
  1132. package/src/ui/pages/ThemeSamplePage.tsx +758 -520
  1133. package/src/ui/pages/__tests__/ArchivePage.test.tsx +72 -0
  1134. package/src/ui/shared/CollectionDirectory.tsx +232 -37
  1135. package/src/ui/shared/CollectionsManager.tsx +155 -75
  1136. package/src/ui/shared/DecorativeQuoteMark.tsx +12 -9
  1137. package/src/ui/shared/MediaGallery.tsx +350 -256
  1138. package/src/ui/shared/Pagination.tsx +36 -25
  1139. package/src/ui/shared/PostFooter.tsx +139 -96
  1140. package/src/ui/shared/__tests__/media-gallery.test.ts +27 -0
  1141. package/src/ui/shared/__tests__/navigation-labels.test.ts +79 -28
  1142. package/src/ui/shared/__tests__/post-footer.test.ts +42 -17
  1143. package/src/ui/shared/collection-management-labels.ts +109 -33
  1144. package/src/ui/shared/navigation-labels.ts +78 -8
  1145. package/bin/lib/site-localize-media.js +0 -427
  1146. package/dist/client/_assets/client-auth.js +0 -3251
  1147. package/dist/client/_assets/client.css +0 -2
  1148. package/dist/client/_assets/client.js +0 -380
  1149. package/src/__tests__/site-localize-media.test.ts +0 -150
  1150. package/src/client/components/jant-collection-sidebar.ts +0 -803
  1151. package/src/i18n/locales/en.ts +0 -1
  1152. package/src/i18n/locales/zh-Hans.po +0 -3183
  1153. package/src/i18n/locales/zh-Hans.ts +0 -1
  1154. package/src/i18n/locales/zh-Hant.po +0 -3183
  1155. package/src/i18n/locales/zh-Hant.ts +0 -1
  1156. package/src/routes/feed/rss.ts +0 -216
  1157. package/src/types/lingui-react-macro.d.ts +0 -34
  1158. /package/dist/client/_assets/chunks/{heic-to-XcUDQvtx.js → heic-to-DUUaO23q.js} +0 -0
  1159. /package/dist/client/_assets/chunks/{module-ChVQstFd.js → module-DcsAZQZ_.js} +0 -0
  1160. /package/dist/client/_assets/chunks/{native-CR5HLOyf.js → native-DpcrFAPh.js} +0 -0
  1161. /package/dist/client/_assets/{client-cjk.css → client-cjk-B7Z0snDu.css} +0 -0
  1162. /package/dist/client/_assets/{client-cjk-tc.css → client-cjk-tc-BesJYrb2.css} +0 -0
@@ -9,6 +9,7 @@
9
9
  import {
10
10
  eq,
11
11
  and,
12
+ or,
12
13
  type SQL,
13
14
  type SQLWrapper,
14
15
  isNull,
@@ -31,9 +32,10 @@ import {
31
32
  } from "../db/schema-bundle.js";
32
33
  import { createEntityId } from "../lib/ids.js";
33
34
  import { now } from "../lib/time.js";
34
- import { renderTiptapJson } from "../lib/tiptap-render.js";
35
+ import { renderTiptapJson, trimTiptapBody } from "../lib/tiptap-render.js";
35
36
  import { extractSummary, extractBodyText } from "../lib/summary.js";
36
37
  import { markdownToTiptapJson } from "../lib/markdown-to-tiptap.js";
38
+ import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
37
39
  import { generatePostSlug } from "../lib/slug.js";
38
40
  import { getSlugValidationIssue } from "../lib/slug-format.js";
39
41
  import { normalizePath, slugify } from "../lib/url.js";
@@ -64,6 +66,12 @@ import {
64
66
  NotFoundError,
65
67
  } from "../lib/errors.js";
66
68
  import { createPathService, type PathService } from "./path.js";
69
+ import {
70
+ extractYouTubeVideoId,
71
+ getYouTubeThumbnailUrls,
72
+ } from "../lib/youtube.js";
73
+ import { getPreviewStorageKey } from "../lib/upload.js";
74
+ import { generateRandomId } from "../lib/nanoid.js";
67
75
 
68
76
  /** Dependencies for operations that coordinate with other services */
69
77
  export interface PostDeleteDeps {
@@ -83,6 +91,7 @@ export interface PostFilters {
83
91
  pinned?: boolean;
84
92
  featured?: boolean;
85
93
  collectionId?: string;
94
+ collectionIds?: string[];
86
95
  /** Exclude posts that are replies (have replyToId set) */
87
96
  excludeReplies?: boolean;
88
97
  /** Exclude posts hidden from Latest from results */
@@ -138,8 +147,18 @@ export interface CollectionFeedEntry {
138
147
  collectedAt: number;
139
148
  }
140
149
 
150
+ export interface PostBodyContent {
151
+ id: string;
152
+ type: "post";
153
+ format: Format;
154
+ contentFormat: "markdown";
155
+ content: string;
156
+ chars: number;
157
+ }
158
+
141
159
  export interface PostService {
142
160
  getById(id: string): Promise<Post | null>;
161
+ getBodyContent(id: string): Promise<PostBodyContent | null>;
143
162
  getBySlug(slug: string): Promise<Post | null>;
144
163
  suggestSlug(input: {
145
164
  title?: string;
@@ -150,6 +169,8 @@ export interface PostService {
150
169
  list(filters?: PostFilters): Promise<Post[]>;
151
170
  /** Count posts matching filters (ignores cursor, offset, limit) */
152
171
  count(filters?: PostFilters): Promise<number>;
172
+ /** Count posts matching filters up to a fixed limit (ignores cursor, offset, limit) */
173
+ countUpTo(filters: PostFilters | undefined, limit: number): Promise<number>;
153
174
  /** Count posts grouped by published year-month (YYYY-MM) */
154
175
  countByYearMonth(
155
176
  filters?: PostFilters,
@@ -161,6 +182,24 @@ export interface PostService {
161
182
  deps: PostAttachmentDeps,
162
183
  summaryConfig?: SummaryConfig,
163
184
  ): Promise<Post>;
185
+ /**
186
+ * Atomically create a thread of posts. The first item is the root; each
187
+ * subsequent item is automatically chained as a reply to the previous one.
188
+ * On failure, all already-created posts are rolled back.
189
+ *
190
+ * @param items - Ordered list of (data, attachments) pairs; at least 2 required
191
+ * @param deps - Media/storage dependencies
192
+ * @param summaryConfig - Optional summary extraction config
193
+ * @returns Ordered list of created posts; posts[0] is the root
194
+ */
195
+ createThreadWithAttachments(
196
+ items: Array<{
197
+ data: CreatePost;
198
+ attachments: PostAttachmentInput[] | undefined;
199
+ }>,
200
+ deps: PostAttachmentDeps,
201
+ summaryConfig?: SummaryConfig,
202
+ ): Promise<Post[]>;
164
203
  update(
165
204
  id: string,
166
205
  data: UpdatePost,
@@ -181,6 +220,11 @@ export interface PostService {
181
220
  * @param deps - Media service and optional storage driver for file cleanup
182
221
  */
183
222
  delete(id: string, deps?: PostDeleteDeps): Promise<boolean>;
223
+ /**
224
+ * Delete a thread draft and release its slug paths so they can be reused.
225
+ * Used when replacing a saved thread draft with a new version.
226
+ */
227
+ deleteThreadDraft(id: string, deps?: PostDeleteDeps): Promise<boolean>;
184
228
  getThread(rootId: string): Promise<Post[]>;
185
229
  updateThreadStatusAndVisibility(
186
230
  rootId: string,
@@ -207,16 +251,31 @@ export interface PostService {
207
251
  collectionId: string,
208
252
  options?: ThreadRootPageOptions,
209
253
  ): Promise<number>;
254
+ /** Count distinct thread roots that contain published posts in any of the given collections */
255
+ countCollectionThreadRootsForCollections(
256
+ collectionIds: string[],
257
+ options?: ThreadRootPageOptions,
258
+ ): Promise<number>;
210
259
  /** List collection thread root IDs ordered by collected-at or rating semantics */
211
260
  listCollectionThreadRootIds(
212
261
  collectionId: string,
213
262
  options?: CollectionThreadRootPageOptions,
214
263
  ): Promise<string[]>;
264
+ /** List collection thread root IDs for a union of collections */
265
+ listCollectionThreadRootIdsForCollections(
266
+ collectionIds: string[],
267
+ options?: CollectionThreadRootPageOptions,
268
+ ): Promise<string[]>;
215
269
  /** List collection feed entries ordered by latest added-at timestamp */
216
270
  listCollectionFeedEntries(
217
271
  collectionId: string,
218
272
  options?: ThreadRootPageOptions,
219
273
  ): Promise<CollectionFeedEntry[]>;
274
+ /** List collection feed entries for a union of collections */
275
+ listCollectionFeedEntriesForCollections(
276
+ collectionIds: string[],
277
+ options?: ThreadRootPageOptions,
278
+ ): Promise<CollectionFeedEntry[]>;
220
279
  /** Fetch all published, non-deleted posts for each requested thread root */
221
280
  getPublishedThreads(rootIds: string[]): Promise<Map<string, Post[]>>;
222
281
  /** For each thread, return post IDs that belong to the given collection */
@@ -224,6 +283,11 @@ export interface PostService {
224
283
  collectionId: string,
225
284
  threadIds: string[],
226
285
  ): Promise<Map<string, string[]>>;
286
+ /** For each thread, return post IDs that belong to any of the given collections */
287
+ getCollectionPostIdsByThreadForCollections(
288
+ collectionIds: string[],
289
+ threadIds: string[],
290
+ ): Promise<Map<string, string[]>>;
227
291
  /** Get distinct years that have published posts */
228
292
  getDistinctYears(filters?: PostFilters): Promise<number[]>;
229
293
  /** For each thread ID, return the ID of the last published, non-deleted post */
@@ -236,24 +300,8 @@ function isValidSlug(value: string): boolean {
236
300
  return SLUG_RE.test(value);
237
301
  }
238
302
 
239
- /** Check if an error (or any of its causes) is a SQLite UNIQUE constraint violation */
240
- function isUniqueConstraintError(err: unknown): boolean {
241
- let current: unknown = err;
242
- while (current) {
243
- const msg = String(current);
244
- if (
245
- msg.includes("UNIQUE constraint") ||
246
- msg.includes("SQLITE_CONSTRAINT")
247
- ) {
248
- return true;
249
- }
250
- current =
251
- current instanceof Error && current.cause !== current
252
- ? current.cause
253
- : undefined;
254
- }
255
- return false;
256
- }
303
+ // Re-export shared constraint detection see db/dialect.ts
304
+ import { isUniqueConstraintError } from "../db/dialect.js";
257
305
 
258
306
  function hasNonEmptyText(value: string | null | undefined): boolean {
259
307
  return typeof value === "string" && value.trim().length > 0;
@@ -326,8 +374,10 @@ function ensurePostRating(
326
374
  function assertPostFormatShape(data: {
327
375
  format: Format;
328
376
  title?: string | null;
377
+ body?: unknown;
329
378
  url?: string | null;
330
379
  quoteText?: string | null;
380
+ hasAttachments?: boolean;
331
381
  }): void {
332
382
  const hasTitle = hasNonEmptyText(data.title);
333
383
  const hasUrl = hasNonEmptyText(data.url);
@@ -340,6 +390,18 @@ function assertPostFormatShape(data: {
340
390
  if (hasQuoteText) {
341
391
  throw new ValidationError("Notes can't include quoted text.");
342
392
  }
393
+ // hasAttachments === undefined means unknown (e.g. update without
394
+ // attachment changes) — skip the empty-content check in that case.
395
+ if (
396
+ !hasTitle &&
397
+ !data.body &&
398
+ data.hasAttachments !== undefined &&
399
+ !data.hasAttachments
400
+ ) {
401
+ throw new ValidationError(
402
+ "Notes need a title, body, or at least one attachment.",
403
+ );
404
+ }
343
405
  return;
344
406
  }
345
407
 
@@ -470,6 +532,124 @@ export function createPostService(
470
532
  .where(and(eq(posts.siteId, siteId), eq(posts.id, rootId)));
471
533
  }
472
534
 
535
+ function normalizeCollectionIds(collectionIds: readonly string[]): string[] {
536
+ return [...new Set(collectionIds)];
537
+ }
538
+
539
+ /**
540
+ * Fetch a link preview thumbnail and store it.
541
+ * Silently returns without updating if the URL is not a recognized provider
542
+ * or if the fetch fails.
543
+ */
544
+ async function resolveAndStorePreview(
545
+ postId: string,
546
+ format: string,
547
+ url: string | null | undefined,
548
+ storage: StorageDriver | null | undefined,
549
+ ): Promise<void> {
550
+ if (!storage || format !== "link" || !url) return;
551
+
552
+ const videoId = extractYouTubeVideoId(url);
553
+ if (!videoId) return;
554
+
555
+ const thumbnailUrls = getYouTubeThumbnailUrls(videoId);
556
+ let imageBytes: Uint8Array | null = null;
557
+ let contentType = "image/jpeg";
558
+
559
+ for (const thumbUrl of thumbnailUrls) {
560
+ try {
561
+ const response = await fetch(thumbUrl);
562
+ if (response.ok) {
563
+ const ct = response.headers.get("content-type");
564
+ // YouTube returns a grey placeholder (120×90) when maxresdefault
565
+ // is not available. Detect it by checking content-length.
566
+ const cl = response.headers.get("content-length");
567
+ if (cl && parseInt(cl, 10) < 5000) {
568
+ // Too small — likely a placeholder, try next quality
569
+ continue;
570
+ }
571
+ imageBytes = new Uint8Array(await response.arrayBuffer());
572
+ if (ct) contentType = ct;
573
+ break;
574
+ }
575
+ } catch {
576
+ // Network failure — try next URL
577
+ }
578
+ }
579
+
580
+ if (!imageBytes) return;
581
+
582
+ const ext = contentType.includes("webp")
583
+ ? "webp"
584
+ : contentType.includes("png")
585
+ ? "png"
586
+ : "jpg";
587
+ const suffix = generateRandomId(8);
588
+ const storageKey = getPreviewStorageKey(siteId, postId, suffix, ext);
589
+
590
+ try {
591
+ await storage.put(storageKey, imageBytes, { contentType });
592
+ await db
593
+ .update(posts)
594
+ .set({
595
+ previewImageKey: storageKey,
596
+ previewKind: "video",
597
+ previewProvider: "youtube",
598
+ })
599
+ .where(and(eq(posts.siteId, siteId), eq(posts.id, postId)));
600
+ } catch {
601
+ // Storage or DB failure — preview is best-effort
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Delete a preview image from storage if it exists.
607
+ */
608
+ async function deletePreviewImage(
609
+ previewImageKey: string | null | undefined,
610
+ storage: StorageDriver | null | undefined,
611
+ ): Promise<void> {
612
+ if (!storage || !previewImageKey) return;
613
+ try {
614
+ await storage.delete(previewImageKey);
615
+ } catch {
616
+ // Best-effort cleanup
617
+ }
618
+ }
619
+
620
+ function buildCollectionMembershipCondition(
621
+ collectionIds: readonly string[],
622
+ ): SQL<unknown> {
623
+ const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
624
+ const firstCollectionId = uniqueCollectionIds[0];
625
+ if (!firstCollectionId) {
626
+ return sql`0 = 1`;
627
+ }
628
+
629
+ return uniqueCollectionIds.length === 1
630
+ ? eq(postCollections.collectionId, firstCollectionId)
631
+ : inArray(postCollections.collectionId, uniqueCollectionIds);
632
+ }
633
+
634
+ function buildPostCollectionSubqueryCondition(
635
+ collectionIds: readonly string[],
636
+ ): SQL<unknown> {
637
+ const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
638
+ if (uniqueCollectionIds.length === 0) {
639
+ return sql`0 = 1`;
640
+ }
641
+
642
+ const placeholders = uniqueCollectionIds.map(
643
+ (collectionId) => sql`${collectionId}`,
644
+ );
645
+ return sql`${posts.id} IN (
646
+ SELECT post_id
647
+ FROM post_collection
648
+ WHERE site_id = ${siteId}
649
+ AND collection_id IN (${sql.join(placeholders, sql`, `)})
650
+ )`;
651
+ }
652
+
473
653
  /** Build WHERE conditions from filters (shared by list and count) */
474
654
  function buildFilterConditions(filters: PostFilters) {
475
655
  const conditions = [eq(posts.siteId, siteId)];
@@ -503,15 +683,13 @@ export function createPostService(
503
683
  if (filters.format) {
504
684
  conditions.push(eq(posts.format, filters.format));
505
685
  }
506
- if (filters.collectionId !== undefined) {
507
- // Filter by collection via junction table
686
+ if (filters.collectionIds !== undefined) {
508
687
  conditions.push(
509
- sql`${posts.id} IN (
510
- SELECT post_id
511
- FROM post_collection
512
- WHERE site_id = ${siteId}
513
- AND collection_id = ${filters.collectionId}
514
- )`,
688
+ buildPostCollectionSubqueryCondition(filters.collectionIds),
689
+ );
690
+ } else if (filters.collectionId !== undefined) {
691
+ conditions.push(
692
+ buildPostCollectionSubqueryCondition([filters.collectionId]),
515
693
  );
516
694
  }
517
695
  if (filters.threadId) {
@@ -573,6 +751,25 @@ export function createPostService(
573
751
  return conditions;
574
752
  }
575
753
 
754
+ async function getLastLivePostIdInThread(
755
+ threadId: string,
756
+ ): Promise<string | null> {
757
+ const rows = await db
758
+ .select({ id: posts.id })
759
+ .from(posts)
760
+ .where(
761
+ and(
762
+ eq(posts.siteId, siteId),
763
+ eq(posts.threadId, threadId),
764
+ isNull(posts.deletedAt),
765
+ ),
766
+ )
767
+ .orderBy(desc(posts.createdAt), desc(posts.id))
768
+ .limit(1);
769
+
770
+ return rows[0]?.id ?? null;
771
+ }
772
+
576
773
  function getCursorSortTimestamp(row: typeof posts.$inferSelect): number {
577
774
  return row.status === "draft" ? row.updatedAt : (row.lastActivityAt ?? -1);
578
775
  }
@@ -732,6 +929,9 @@ export function createPostService(
732
929
  quoteText: row.quoteText,
733
930
  summary: row.summary,
734
931
  rating: ensurePostRating(row.rating, Error),
932
+ previewImageKey: row.previewImageKey,
933
+ previewKind: row.previewKind,
934
+ previewProvider: row.previewProvider,
735
935
  replyToId: row.replyToId,
736
936
  threadId: row.threadId,
737
937
  deletedAt: row.deletedAt,
@@ -923,6 +1123,24 @@ export function createPostService(
923
1123
  return rows.map((row) => row.collectionId);
924
1124
  }
925
1125
 
1126
+ async function getPostCollectionTimestamps(
1127
+ postId: string,
1128
+ ): Promise<Map<string, number>> {
1129
+ const rows = await db
1130
+ .select({
1131
+ collectionId: postCollections.collectionId,
1132
+ createdAt: postCollections.createdAt,
1133
+ })
1134
+ .from(postCollections)
1135
+ .where(
1136
+ and(
1137
+ eq(postCollections.siteId, siteId),
1138
+ eq(postCollections.postId, postId),
1139
+ ),
1140
+ );
1141
+ return new Map(rows.map((r) => [r.collectionId, r.createdAt]));
1142
+ }
1143
+
926
1144
  function buildRollbackUpdate(
927
1145
  post: Post,
928
1146
  collectionIds: string[],
@@ -960,6 +1178,20 @@ export function createPostService(
960
1178
  return hydratePost(result[0]);
961
1179
  },
962
1180
 
1181
+ async getBodyContent(id) {
1182
+ const post = await this.getById(id);
1183
+ if (!post) return null;
1184
+
1185
+ return {
1186
+ id: post.id,
1187
+ type: "post",
1188
+ format: post.format,
1189
+ contentFormat: "markdown",
1190
+ content: post.body ? tiptapJsonToMarkdown(post.body) : "",
1191
+ chars: post.bodyText?.length ?? 0,
1192
+ };
1193
+ },
1194
+
963
1195
  async getBySlug(slug) {
964
1196
  const resolved = await resolvedPaths.resolve(slug);
965
1197
  if (!resolved || resolved.kind !== "slug" || !resolved.postId) {
@@ -1015,6 +1247,14 @@ export function createPostService(
1015
1247
  ELSE 1
1016
1248
  END`;
1017
1249
 
1250
+ // NULL-sort order differs between dialects: SQLite puts NULLs last for
1251
+ // DESC, Postgres puts them first. Wrap nullable sort keys in COALESCE
1252
+ // so pinned/featured posts land on top under both engines. Mirrors the
1253
+ // expressions used by `buildListCursorCondition` above.
1254
+ const pinnedSortExpr = sql<number>`coalesce(${posts.pinnedAt}, -1)`;
1255
+ const featuredSortExpr = sql<number>`coalesce(${posts.featuredAt}, -1)`;
1256
+ const sortTimestampSortExpr = sql<number>`coalesce(${sortTimestamp}, -1)`;
1257
+
1018
1258
  const baseQuery = db
1019
1259
  .select()
1020
1260
  .from(posts)
@@ -1024,29 +1264,31 @@ export function createPostService(
1024
1264
  let query =
1025
1265
  filters.featured || filters.sortOrder === undefined
1026
1266
  ? baseQuery.orderBy(
1027
- desc(posts.pinnedAt),
1028
- filters.featured ? desc(posts.featuredAt) : desc(sortTimestamp),
1267
+ desc(pinnedSortExpr),
1268
+ filters.featured
1269
+ ? desc(featuredSortExpr)
1270
+ : desc(sortTimestampSortExpr),
1029
1271
  desc(posts.id),
1030
1272
  )
1031
1273
  : filters.sortOrder === "oldest"
1032
1274
  ? baseQuery.orderBy(
1033
- desc(posts.pinnedAt),
1034
- asc(sortTimestamp),
1275
+ desc(pinnedSortExpr),
1276
+ asc(sortTimestampSortExpr),
1035
1277
  asc(posts.id),
1036
1278
  )
1037
1279
  : filters.sortOrder === "rating_desc"
1038
1280
  ? baseQuery.orderBy(
1039
- desc(posts.pinnedAt),
1281
+ desc(pinnedSortExpr),
1040
1282
  desc(ratingPresence),
1041
1283
  desc(posts.rating),
1042
- desc(sortTimestamp),
1284
+ desc(sortTimestampSortExpr),
1043
1285
  desc(posts.id),
1044
1286
  )
1045
1287
  : baseQuery.orderBy(
1046
- desc(posts.pinnedAt),
1288
+ desc(pinnedSortExpr),
1047
1289
  desc(ratingPresence),
1048
1290
  asc(posts.rating),
1049
- desc(sortTimestamp),
1291
+ desc(sortTimestampSortExpr),
1050
1292
  desc(posts.id),
1051
1293
  );
1052
1294
 
@@ -1062,13 +1304,29 @@ export function createPostService(
1062
1304
  const conditions = buildFilterConditions(filters);
1063
1305
 
1064
1306
  const result = await db
1065
- .select({ count: sql<number>`count(*)`.as("count") })
1307
+ .select({ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count") })
1066
1308
  .from(posts)
1067
1309
  .where(conditions.length > 0 ? and(...conditions) : undefined);
1068
1310
 
1069
1311
  return result[0]?.count ?? 0;
1070
1312
  },
1071
1313
 
1314
+ async countUpTo(filters = {}, limit) {
1315
+ const normalizedLimit = Math.max(0, Math.trunc(limit));
1316
+ if (normalizedLimit === 0) {
1317
+ return 0;
1318
+ }
1319
+
1320
+ const conditions = buildFilterConditions(filters);
1321
+ const rows = await db
1322
+ .select({ id: posts.id })
1323
+ .from(posts)
1324
+ .where(conditions.length > 0 ? and(...conditions) : undefined)
1325
+ .limit(normalizedLimit);
1326
+
1327
+ return rows.length;
1328
+ },
1329
+
1072
1330
  async countByYearMonth(filters = {}) {
1073
1331
  const conditions = [
1074
1332
  ...buildFilterConditions(filters),
@@ -1079,7 +1337,7 @@ export function createPostService(
1079
1337
  return db
1080
1338
  .select({
1081
1339
  yearMonth: publishedYearMonthExpr.as("year_month"),
1082
- count: sql<number>`count(*)`.as("count"),
1340
+ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
1083
1341
  })
1084
1342
  .from(posts)
1085
1343
  .where(conditions.length > 0 ? and(...conditions) : undefined)
@@ -1099,22 +1357,31 @@ export function createPostService(
1099
1357
  : undefined;
1100
1358
  const rating = ensurePostRating(data.rating);
1101
1359
 
1360
+ const rawBody = data.bodyMarkdown
1361
+ ? markdownToTiptapJson(data.bodyMarkdown)
1362
+ : (data.body ?? null);
1363
+ const body = rawBody ? trimTiptapBody(rawBody) : null;
1364
+ const title = data.title?.trim() || null;
1365
+ const quoteText = data.quoteText?.trim() || null;
1366
+ const url = data.url?.trim() || null;
1367
+
1102
1368
  assertPostFormatShape({
1103
1369
  format,
1104
- title: data.title,
1105
- url: data.url,
1106
- quoteText: data.quoteText,
1370
+ title,
1371
+ body,
1372
+ url,
1373
+ quoteText,
1374
+ hasAttachments: data.attachments
1375
+ ? data.attachments.length > 0
1376
+ : undefined,
1107
1377
  });
1108
1378
 
1109
- const body = data.bodyMarkdown
1110
- ? markdownToTiptapJson(data.bodyMarkdown)
1111
- : (data.body ?? null);
1112
1379
  const bodyHtml = body ? renderTiptapJson(body) : null;
1113
1380
  const bodyText = body ? extractBodyText(body) : null;
1114
1381
 
1115
1382
  // Generate summary for titled notes with body content
1116
1383
  let summary: string | null = null;
1117
- if (format === "note" && data.title && body && summaryConfig) {
1384
+ if (format === "note" && title && body && summaryConfig) {
1118
1385
  summary = extractSummary(
1119
1386
  body,
1120
1387
  summaryConfig.maxParagraphs,
@@ -1127,18 +1394,45 @@ export function createPostService(
1127
1394
  let status: Status = requestedStatus ?? "published";
1128
1395
  let visibility: Visibility | null = requestedVisibility ?? "public";
1129
1396
 
1397
+ // Collapse the two-input (`flag` + `flagAt`) pair the DTO exposes
1398
+ // into the single timestamp form the DB column stores. Explicit
1399
+ // `*At` wins over the boolean shorthand when both are set.
1400
+ const resolvedPinnedAt =
1401
+ data.pinnedAt !== undefined
1402
+ ? data.pinnedAt
1403
+ : data.pinned !== undefined
1404
+ ? data.pinned
1405
+ ? timestamp
1406
+ : null
1407
+ : null;
1408
+ const resolvedFeaturedAt =
1409
+ data.featuredAt !== undefined
1410
+ ? data.featuredAt
1411
+ : data.featured !== undefined
1412
+ ? data.featured
1413
+ ? timestamp
1414
+ : null
1415
+ : null;
1416
+
1130
1417
  if (data.replyToId) {
1131
1418
  const parent = await this.getById(data.replyToId);
1132
1419
  if (!parent) {
1133
1420
  throw new NotFoundError("Parent post");
1134
1421
  }
1135
1422
 
1136
- if (data.pinned) {
1423
+ if (resolvedPinnedAt !== null) {
1137
1424
  throw new ConflictError(
1138
1425
  "Cannot pin a thread reply. Pin the root post instead.",
1139
1426
  );
1140
1427
  }
1141
1428
 
1429
+ const lastLivePostId = await getLastLivePostIdInThread(parent.threadId);
1430
+ if (lastLivePostId && lastLivePostId !== parent.id) {
1431
+ throw new ConflictError(
1432
+ "This post is no longer the end of the thread. Reply to the latest post instead.",
1433
+ );
1434
+ }
1435
+
1142
1436
  threadId = parent.threadId;
1143
1437
 
1144
1438
  // Replies inherit visibility from the root at read time.
@@ -1162,6 +1456,14 @@ export function createPostService(
1162
1456
  let slug: string;
1163
1457
  let aliasPath: string | null = null;
1164
1458
 
1459
+ // Quote `title` stores source attribution (e.g. author name), not a real
1460
+ // title — deriving the slug from it would produce URLs like `/basho`
1461
+ // that read as "a page about Basho" and collide across every quote from
1462
+ // the same source. Fall through to random IDs instead; users who want a
1463
+ // readable slug can still set one explicitly.
1464
+ const titleForSlug =
1465
+ format === "quote" ? undefined : (title ?? undefined);
1466
+
1165
1467
  if (data.path) {
1166
1468
  const normalized = normalizePath(data.path);
1167
1469
  if (isValidSlug(normalized)) {
@@ -1176,7 +1478,7 @@ export function createPostService(
1176
1478
  const slugified = slugify(normalized);
1177
1479
  slug = await generatePostSlug({
1178
1480
  slug: slugified || undefined,
1179
- title: data.title,
1481
+ title: titleForSlug,
1180
1482
  idLength: config.slugIdLength,
1181
1483
  isAvailable: isSlugAvailable,
1182
1484
  });
@@ -1189,13 +1491,61 @@ export function createPostService(
1189
1491
  } else {
1190
1492
  slug = await generatePostSlug({
1191
1493
  slug: data.slug,
1192
- title: data.title,
1494
+ title: titleForSlug,
1193
1495
  idLength: config.slugIdLength,
1194
1496
  isAvailable: isSlugAvailable,
1195
1497
  });
1196
1498
  }
1197
1499
 
1198
- const collectionIds = [...new Set(data.collectionIds ?? [])];
1500
+ // When structured `collectionEntries` are provided (Hugo import path),
1501
+ // they win over the bare `collectionIds` slug list and carry
1502
+ // `createdAt` / `position` / `pinnedAt` per row. Otherwise fall back to
1503
+ // the simple list and derive those fields from sensible defaults.
1504
+ const hasCollectionEntries =
1505
+ data.collectionEntries !== undefined &&
1506
+ data.collectionEntries.length > 0;
1507
+ const collectionInsertRows: {
1508
+ siteId: string;
1509
+ postId: string;
1510
+ collectionId: string;
1511
+ createdAt: number;
1512
+ position: number;
1513
+ pinnedAt: number | null;
1514
+ }[] = hasCollectionEntries
1515
+ ? (() => {
1516
+ const seen = new Set<string>();
1517
+ const rows: {
1518
+ siteId: string;
1519
+ postId: string;
1520
+ collectionId: string;
1521
+ createdAt: number;
1522
+ position: number;
1523
+ pinnedAt: number | null;
1524
+ }[] = [];
1525
+ let fallbackPosition = 0;
1526
+ for (const entry of data.collectionEntries ?? []) {
1527
+ if (seen.has(entry.collectionId)) continue;
1528
+ seen.add(entry.collectionId);
1529
+ rows.push({
1530
+ siteId,
1531
+ postId: id,
1532
+ collectionId: entry.collectionId,
1533
+ createdAt: entry.createdAt ?? timestamp,
1534
+ position: entry.position ?? fallbackPosition,
1535
+ pinnedAt: entry.pinnedAt ?? null,
1536
+ });
1537
+ fallbackPosition++;
1538
+ }
1539
+ return rows;
1540
+ })()
1541
+ : [...new Set(data.collectionIds ?? [])].map((collectionId, index) => ({
1542
+ siteId,
1543
+ postId: id,
1544
+ collectionId,
1545
+ createdAt: timestamp,
1546
+ position: index,
1547
+ pinnedAt: null,
1548
+ }));
1199
1549
 
1200
1550
  try {
1201
1551
  if (usesBatchWrites) {
@@ -1208,14 +1558,14 @@ export function createPostService(
1208
1558
  format,
1209
1559
  status,
1210
1560
  visibility,
1211
- pinnedAt: data.pinned ? timestamp : null,
1212
- featuredAt: data.featured ? timestamp : null,
1213
- title: data.title ?? null,
1214
- url: data.url ?? null,
1215
- body: body ?? null,
1561
+ pinnedAt: resolvedPinnedAt,
1562
+ featuredAt: resolvedFeaturedAt,
1563
+ title,
1564
+ url,
1565
+ body,
1216
1566
  bodyHtml,
1217
1567
  bodyText,
1218
- quoteText: data.quoteText ?? null,
1568
+ quoteText,
1219
1569
  summary,
1220
1570
  rating,
1221
1571
  replyToId: data.replyToId ?? null,
@@ -1259,16 +1609,9 @@ export function createPostService(
1259
1609
  );
1260
1610
  }
1261
1611
 
1262
- if (collectionIds.length > 0) {
1612
+ if (collectionInsertRows.length > 0) {
1263
1613
  writeQueries.push(
1264
- db.insert(postCollections).values(
1265
- collectionIds.map((collectionId) => ({
1266
- siteId,
1267
- postId: id,
1268
- collectionId,
1269
- createdAt: timestamp,
1270
- })),
1271
- ),
1614
+ db.insert(postCollections).values(collectionInsertRows),
1272
1615
  );
1273
1616
  }
1274
1617
 
@@ -1286,14 +1629,14 @@ export function createPostService(
1286
1629
  format,
1287
1630
  status,
1288
1631
  visibility,
1289
- pinnedAt: data.pinned ? timestamp : null,
1290
- featuredAt: data.featured ? timestamp : null,
1291
- title: data.title ?? null,
1292
- url: data.url ?? null,
1293
- body: body ?? null,
1632
+ pinnedAt: resolvedPinnedAt,
1633
+ featuredAt: resolvedFeaturedAt,
1634
+ title,
1635
+ url,
1636
+ body,
1294
1637
  bodyHtml,
1295
1638
  bodyText,
1296
- quoteText: data.quoteText ?? null,
1639
+ quoteText,
1297
1640
  summary,
1298
1641
  rating,
1299
1642
  replyToId: data.replyToId ?? null,
@@ -1332,15 +1675,8 @@ export function createPostService(
1332
1675
  });
1333
1676
  }
1334
1677
 
1335
- if (collectionIds.length > 0) {
1336
- await tx.insert(postCollections).values(
1337
- collectionIds.map((collectionId) => ({
1338
- siteId,
1339
- postId: id,
1340
- collectionId,
1341
- createdAt: timestamp,
1342
- })),
1343
- );
1678
+ if (collectionInsertRows.length > 0) {
1679
+ await tx.insert(postCollections).values(collectionInsertRows);
1344
1680
  }
1345
1681
  });
1346
1682
  }
@@ -1360,7 +1696,7 @@ export function createPostService(
1360
1696
  }
1361
1697
 
1362
1698
  // Bump thread root's lastActivityAt when creating a published reply
1363
- if (data.replyToId && status === "published") {
1699
+ if (data.replyToId && status === "published" && !data.quietReply) {
1364
1700
  await recalculateThreadLastActivity(threadId);
1365
1701
  }
1366
1702
 
@@ -1373,13 +1709,25 @@ export function createPostService(
1373
1709
  await createAttachmentMediaIds(attachmentInputs, deps);
1374
1710
 
1375
1711
  try {
1376
- const post = await this.create(data, summaryConfig);
1712
+ const post = await this.create(
1713
+ { ...data, attachments: attachmentInputs },
1714
+ summaryConfig,
1715
+ );
1377
1716
 
1378
1717
  try {
1379
1718
  if (orderedMediaIds.length > 0) {
1380
1719
  await deps.media.attachToPost(post.id, orderedMediaIds);
1381
1720
  }
1382
1721
  await applyAttachmentAltUpdates(attachmentInputs, deps);
1722
+
1723
+ // Best-effort: fetch and store link preview thumbnail
1724
+ await resolveAndStorePreview(
1725
+ post.id,
1726
+ post.format,
1727
+ post.url,
1728
+ deps.storage,
1729
+ );
1730
+
1383
1731
  return post;
1384
1732
  } catch (error) {
1385
1733
  await deps.media.attachToPost(post.id, []).catch(() => undefined);
@@ -1396,6 +1744,47 @@ export function createPostService(
1396
1744
  }
1397
1745
  },
1398
1746
 
1747
+ async createThreadWithAttachments(items, deps, summaryConfig) {
1748
+ if (items.length < 2) {
1749
+ throw new ValidationError("A thread requires at least 2 posts.");
1750
+ }
1751
+
1752
+ const created: Post[] = [];
1753
+
1754
+ for (let i = 0; i < items.length; i++) {
1755
+ const item = items[i];
1756
+ if (!item) continue;
1757
+ const { data, attachments } = item;
1758
+ const prevPost = created[i - 1];
1759
+ const postData: CreatePost = {
1760
+ ...data,
1761
+ // Chain each post as a reply to the previous one (server-side chaining)
1762
+ replyToId: i === 0 ? data.replyToId : prevPost?.id,
1763
+ };
1764
+
1765
+ try {
1766
+ const post = await this.createWithAttachments(
1767
+ postData,
1768
+ attachments,
1769
+ deps,
1770
+ summaryConfig,
1771
+ );
1772
+ created.push(post);
1773
+ } catch (error) {
1774
+ // Rollback: delete all already-created posts in reverse order
1775
+ for (const p of [...created].reverse()) {
1776
+ await this.delete(p.id, {
1777
+ media: deps.media,
1778
+ storage: deps.storage,
1779
+ }).catch(() => undefined);
1780
+ }
1781
+ throw error;
1782
+ }
1783
+ }
1784
+
1785
+ return created;
1786
+ },
1787
+
1399
1788
  async update(id, data, summaryConfig) {
1400
1789
  const existing = await this.getById(id);
1401
1790
  if (!existing) return null;
@@ -1405,9 +1794,12 @@ export function createPostService(
1405
1794
  data.format !== undefined
1406
1795
  ? ensurePostFormat(data.format)
1407
1796
  : existing.format;
1408
- const nextUrl = data.url !== undefined ? data.url : existing.url;
1797
+ const nextUrl =
1798
+ data.url !== undefined ? data.url?.trim() || null : existing.url;
1409
1799
  const nextQuoteText =
1410
- data.quoteText !== undefined ? data.quoteText : existing.quoteText;
1800
+ data.quoteText !== undefined
1801
+ ? data.quoteText?.trim() || null
1802
+ : existing.quoteText;
1411
1803
  const nextStatus =
1412
1804
  data.status !== undefined
1413
1805
  ? ensurePostStatus(data.status)
@@ -1419,11 +1811,26 @@ export function createPostService(
1419
1811
  const nextRating =
1420
1812
  data.rating !== undefined ? ensurePostRating(data.rating) : undefined;
1421
1813
 
1814
+ const nextTitle =
1815
+ data.title !== undefined ? data.title?.trim() || null : existing.title;
1816
+
1422
1817
  assertPostFormatShape({
1423
1818
  format: nextFormat,
1424
- title: data.title !== undefined ? data.title : existing.title,
1819
+ title: nextTitle,
1820
+ body:
1821
+ data.body !== undefined || data.bodyMarkdown !== undefined
1822
+ ? data.bodyMarkdown
1823
+ ? markdownToTiptapJson(data.bodyMarkdown)
1824
+ : (data.body ?? null)
1825
+ : existing.body,
1425
1826
  url: nextUrl,
1426
1827
  quoteText: nextQuoteText,
1828
+ // During update we can't cheaply resolve final attachment state;
1829
+ // existing posts already passed creation validation, so we only
1830
+ // guard against clearing all text fields here.
1831
+ hasAttachments: data.attachments
1832
+ ? data.attachments.length > 0
1833
+ : undefined,
1427
1834
  });
1428
1835
  assertDraftPublishedAt(nextStatus, data.publishedAt);
1429
1836
 
@@ -1446,19 +1853,27 @@ export function createPostService(
1446
1853
  }
1447
1854
 
1448
1855
  if (data.format !== undefined) updates.format = nextFormat;
1449
- if (data.title !== undefined) updates.title = data.title;
1450
- if (data.url !== undefined) updates.url = data.url;
1451
- if (data.quoteText !== undefined) updates.quoteText = data.quoteText;
1856
+ if (data.title !== undefined) updates.title = nextTitle;
1857
+ if (data.url !== undefined) updates.url = nextUrl;
1858
+ if (data.quoteText !== undefined) updates.quoteText = nextQuoteText;
1452
1859
  if (data.rating !== undefined) updates.rating = nextRating;
1453
- if (data.pinned !== undefined)
1454
- updates.pinnedAt = data.pinned ? now() : null;
1455
- if (data.featured !== undefined)
1456
- updates.featuredAt = data.featured ? now() : null;
1860
+ // Prefer explicit timestamp when provided; fall back to boolean shorthand.
1861
+ if (data.pinnedAt !== undefined) {
1862
+ updates.pinnedAt = data.pinnedAt;
1863
+ } else if (data.pinned !== undefined) {
1864
+ updates.pinnedAt = data.pinned ? timestamp : null;
1865
+ }
1866
+ if (data.featuredAt !== undefined) {
1867
+ updates.featuredAt = data.featuredAt;
1868
+ } else if (data.featured !== undefined) {
1869
+ updates.featuredAt = data.featured ? timestamp : null;
1870
+ }
1457
1871
 
1458
1872
  if (data.body !== undefined || data.bodyMarkdown !== undefined) {
1459
- const normalizedBody = data.bodyMarkdown
1873
+ const rawBody = data.bodyMarkdown
1460
1874
  ? markdownToTiptapJson(data.bodyMarkdown)
1461
1875
  : (data.body ?? null);
1876
+ const normalizedBody = rawBody ? trimTiptapBody(rawBody) : null;
1462
1877
  updates.body = normalizedBody;
1463
1878
  updates.bodyHtml = normalizedBody
1464
1879
  ? renderTiptapJson(normalizedBody)
@@ -1471,7 +1886,7 @@ export function createPostService(
1471
1886
  // Recompute summary when body, title, or format change
1472
1887
  if (summaryConfig) {
1473
1888
  const format = nextFormat;
1474
- const title = data.title !== undefined ? data.title : existing.title;
1889
+ const title = nextTitle;
1475
1890
  const body =
1476
1891
  data.bodyMarkdown !== undefined
1477
1892
  ? data.bodyMarkdown
@@ -1498,7 +1913,10 @@ export function createPostService(
1498
1913
  "Cannot change visibility of a thread reply. Update the root post instead.",
1499
1914
  );
1500
1915
  }
1501
- if (data.pinned !== undefined) {
1916
+ if (
1917
+ (data.pinnedAt !== undefined && data.pinnedAt !== null) ||
1918
+ data.pinned === true
1919
+ ) {
1502
1920
  throw new ConflictError(
1503
1921
  "Cannot pin a thread reply. Pin the root post instead.",
1504
1922
  );
@@ -1557,9 +1975,9 @@ export function createPostService(
1557
1975
  }
1558
1976
 
1559
1977
  // Complex case: cascade + update + collection sync atomically
1560
- const existingCollectionIds = needsCollectionSync
1561
- ? await getCollectionIdsForPost(id)
1562
- : [];
1978
+ const existingCollectionTimestamps = needsCollectionSync
1979
+ ? await getPostCollectionTimestamps(id)
1980
+ : new Map<string, number>();
1563
1981
  let updateResult: (typeof posts.$inferSelect)[] | undefined;
1564
1982
 
1565
1983
  if (usesBatchWrites) {
@@ -1614,38 +2032,30 @@ export function createPostService(
1614
2032
  );
1615
2033
 
1616
2034
  if (needsCollectionSync) {
1617
- const existingIds = new Set(existingCollectionIds);
1618
- const nextIds = new Set(nextCollectionIds);
1619
- const removedIds = existingCollectionIds.filter(
1620
- (cid) => !nextIds.has(cid),
1621
- );
1622
- const addedIds = nextCollectionIds.filter(
1623
- (cid) => !existingIds.has(cid),
1624
- );
1625
-
1626
- if (removedIds.length > 0) {
1627
- writeQueries.push(
1628
- db
1629
- .delete(postCollections)
1630
- .where(
1631
- and(
1632
- eq(postCollections.siteId, siteId),
1633
- eq(postCollections.postId, id),
1634
- inArray(postCollections.collectionId, removedIds),
1635
- ),
2035
+ // Delete all and re-insert to preserve user-specified ordering
2036
+ writeQueries.push(
2037
+ db
2038
+ .delete(postCollections)
2039
+ .where(
2040
+ and(
2041
+ eq(postCollections.siteId, siteId),
2042
+ eq(postCollections.postId, id),
1636
2043
  ),
1637
- );
1638
- }
2044
+ ),
2045
+ );
1639
2046
 
1640
- if (addedIds.length > 0) {
2047
+ if (nextCollectionIds.length > 0) {
1641
2048
  const collectionTimestamp = now();
1642
2049
  writeQueries.push(
1643
2050
  db.insert(postCollections).values(
1644
- addedIds.map((collectionId) => ({
2051
+ nextCollectionIds.map((collectionId, index) => ({
1645
2052
  siteId,
1646
2053
  postId: id,
1647
2054
  collectionId,
1648
- createdAt: collectionTimestamp,
2055
+ createdAt:
2056
+ existingCollectionTimestamps.get(collectionId) ??
2057
+ collectionTimestamp,
2058
+ position: index,
1649
2059
  })),
1650
2060
  ),
1651
2061
  );
@@ -1705,35 +2115,27 @@ export function createPostService(
1705
2115
  .returning();
1706
2116
 
1707
2117
  if (needsCollectionSync) {
1708
- const existingIds = new Set(existingCollectionIds);
1709
- const nextIds = new Set(nextCollectionIds);
1710
- const removedIds = existingCollectionIds.filter(
1711
- (cid) => !nextIds.has(cid),
1712
- );
1713
- const addedIds = nextCollectionIds.filter(
1714
- (cid) => !existingIds.has(cid),
1715
- );
1716
-
1717
- if (removedIds.length > 0) {
1718
- await tx
1719
- .delete(postCollections)
1720
- .where(
1721
- and(
1722
- eq(postCollections.siteId, siteId),
1723
- eq(postCollections.postId, id),
1724
- inArray(postCollections.collectionId, removedIds),
1725
- ),
1726
- );
1727
- }
2118
+ // Delete all and re-insert to preserve user-specified ordering
2119
+ await tx
2120
+ .delete(postCollections)
2121
+ .where(
2122
+ and(
2123
+ eq(postCollections.siteId, siteId),
2124
+ eq(postCollections.postId, id),
2125
+ ),
2126
+ );
1728
2127
 
1729
- if (addedIds.length > 0) {
2128
+ if (nextCollectionIds.length > 0) {
1730
2129
  const collectionTimestamp = now();
1731
2130
  await tx.insert(postCollections).values(
1732
- addedIds.map((collectionId) => ({
2131
+ nextCollectionIds.map((collectionId, index) => ({
1733
2132
  siteId,
1734
2133
  postId: id,
1735
2134
  collectionId,
1736
- createdAt: collectionTimestamp,
2135
+ createdAt:
2136
+ existingCollectionTimestamps.get(collectionId) ??
2137
+ collectionTimestamp,
2138
+ position: index,
1737
2139
  })),
1738
2140
  );
1739
2141
  }
@@ -1750,7 +2152,32 @@ export function createPostService(
1750
2152
 
1751
2153
  async updateWithAttachments(id, data, attachments, deps, summaryConfig) {
1752
2154
  if (attachments === undefined) {
1753
- return this.update(id, data, summaryConfig);
2155
+ const existing = await this.getById(id);
2156
+ const post = await this.update(id, data, summaryConfig);
2157
+ if (post && existing) {
2158
+ const urlChanged =
2159
+ data.url !== undefined && data.url !== existing.url;
2160
+ const formatChanged =
2161
+ data.format !== undefined && data.format !== existing.format;
2162
+ if (urlChanged || formatChanged) {
2163
+ await deletePreviewImage(existing.previewImageKey, deps.storage);
2164
+ await db
2165
+ .update(posts)
2166
+ .set({
2167
+ previewImageKey: null,
2168
+ previewKind: null,
2169
+ previewProvider: null,
2170
+ })
2171
+ .where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
2172
+ await resolveAndStorePreview(
2173
+ post.id,
2174
+ post.format,
2175
+ post.url,
2176
+ deps.storage,
2177
+ );
2178
+ }
2179
+ }
2180
+ return post;
1754
2181
  }
1755
2182
 
1756
2183
  const existingPost = await this.getById(id);
@@ -1788,17 +2215,41 @@ export function createPostService(
1788
2215
  await applyAttachmentAltUpdates(attachments, deps);
1789
2216
 
1790
2217
  const nextAttachmentIds = new Set(orderedMediaIds);
1791
- const removedTextAttachmentIds = existingAttachments
1792
- .filter(
1793
- (attachment) =>
1794
- attachment.mimeType === "text/x-tiptap+json" &&
1795
- !nextAttachmentIds.has(attachment.id),
1796
- )
2218
+ const removedAttachmentIds = existingAttachments
2219
+ .filter((attachment) => !nextAttachmentIds.has(attachment.id))
1797
2220
  .map((attachment) => attachment.id);
1798
2221
  await deps.media
1799
- .deleteByIds(removedTextAttachmentIds, deps.storage)
2222
+ .deleteByIds(removedAttachmentIds, deps.storage)
1800
2223
  .catch(() => undefined);
1801
2224
 
2225
+ // Best-effort: update link preview when URL changes
2226
+ const urlChanged =
2227
+ data.url !== undefined && data.url !== existingPost.url;
2228
+ const formatChanged =
2229
+ data.format !== undefined && data.format !== existingPost.format;
2230
+ if (urlChanged || formatChanged) {
2231
+ // Clean up old preview image
2232
+ await deletePreviewImage(existingPost.previewImageKey, deps.storage);
2233
+
2234
+ // Clear preview fields first (may be re-set by resolveAndStorePreview)
2235
+ await db
2236
+ .update(posts)
2237
+ .set({
2238
+ previewImageKey: null,
2239
+ previewKind: null,
2240
+ previewProvider: null,
2241
+ })
2242
+ .where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
2243
+
2244
+ // Fetch new preview if applicable
2245
+ await resolveAndStorePreview(
2246
+ post.id,
2247
+ post.format,
2248
+ post.url,
2249
+ deps.storage,
2250
+ );
2251
+ }
2252
+
1802
2253
  return post;
1803
2254
  } catch (error) {
1804
2255
  if (replacedAttachments) {
@@ -1826,17 +2277,16 @@ export function createPostService(
1826
2277
  const existing = await this.getById(id);
1827
2278
  if (!existing) return false;
1828
2279
 
1829
- // Clean up media for all affected posts
1830
- if (deps?.media) {
1831
- let postIds: string[];
1832
- if (!isThreadReply(existing)) {
1833
- const thread = await this.getThread(id);
1834
- postIds = thread.map((p) => p.id);
1835
- } else {
1836
- postIds = [id];
1837
- }
2280
+ const isRoot = !isThreadReply(existing);
2281
+ const affectedPosts: Post[] = isRoot
2282
+ ? await this.getThread(id)
2283
+ : [existing];
1838
2284
 
1839
- const mediaMap = await deps.media.getByPostIds(postIds);
2285
+ // Clean up media and preview images
2286
+ if (deps?.media) {
2287
+ const mediaMap = await deps.media.getByPostIds(
2288
+ affectedPosts.map((p) => p.id),
2289
+ );
1840
2290
  const allMedia = [...mediaMap.values()].flat();
1841
2291
  if (allMedia.length > 0) {
1842
2292
  await deps.media.deleteByIds(
@@ -1844,18 +2294,20 @@ export function createPostService(
1844
2294
  deps.storage,
1845
2295
  );
1846
2296
  }
2297
+
2298
+ for (const p of affectedPosts) {
2299
+ await deletePreviewImage(p.previewImageKey, deps.storage);
2300
+ }
1847
2301
  }
1848
2302
 
1849
2303
  const timestamp = now();
1850
2304
 
1851
- // If this is a thread root, soft delete all posts in the thread
1852
- if (!isThreadReply(existing)) {
2305
+ if (isRoot) {
1853
2306
  await db
1854
2307
  .update(posts)
1855
2308
  .set({ deletedAt: timestamp, updatedAt: timestamp })
1856
2309
  .where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
1857
2310
  } else {
1858
- // Soft-delete the single reply
1859
2311
  await db
1860
2312
  .update(posts)
1861
2313
  .set({ deletedAt: timestamp, updatedAt: timestamp })
@@ -1866,6 +2318,23 @@ export function createPostService(
1866
2318
  return true;
1867
2319
  },
1868
2320
 
2321
+ async deleteThreadDraft(id, deps) {
2322
+ const deleted = await this.delete(id, deps);
2323
+ if (!deleted) return false;
2324
+
2325
+ // Release path_registry entries for all posts in the thread so slugs
2326
+ // can be reused by the replacement thread.
2327
+ const threadRows = await db
2328
+ .select({ id: posts.id })
2329
+ .from(posts)
2330
+ .where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
2331
+ for (const row of threadRows) {
2332
+ await resolvedPaths.deleteByPostId(row.id);
2333
+ }
2334
+
2335
+ return true;
2336
+ },
2337
+
1869
2338
  async getThread(rootId) {
1870
2339
  const rows = await db
1871
2340
  .select()
@@ -1955,7 +2424,7 @@ export function createPostService(
1955
2424
  const rows = await db
1956
2425
  .select({
1957
2426
  threadId: posts.threadId,
1958
- count: sql<number>`count(*)`.as("count"),
2427
+ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
1959
2428
  })
1960
2429
  .from(posts)
1961
2430
  .where(
@@ -1984,10 +2453,10 @@ export function createPostService(
1984
2453
  id: posts.id,
1985
2454
  threadId: posts.threadId,
1986
2455
  createdAt: posts.createdAt,
1987
- previewRank: sql<number>`ROW_NUMBER() OVER (
2456
+ previewRank: sql<number>`CAST(ROW_NUMBER() OVER (
1988
2457
  PARTITION BY ${posts.threadId}
1989
2458
  ORDER BY ${posts.createdAt}, ${posts.id}
1990
- )`.as("preview_rank"),
2459
+ ) AS INTEGER)`.as("preview_rank"),
1991
2460
  })
1992
2461
  .from(posts)
1993
2462
  .where(
@@ -2041,14 +2510,17 @@ export function createPostService(
2041
2510
  .select({
2042
2511
  id: posts.id,
2043
2512
  threadId: posts.threadId,
2044
- replyToId: posts.replyToId,
2045
- replyRank: sql<number>`ROW_NUMBER() OVER (
2513
+ firstReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
2514
+ PARTITION BY ${posts.threadId}
2515
+ ORDER BY ${posts.createdAt}, ${posts.id}
2516
+ ) AS INTEGER)`.as("first_reply_rank"),
2517
+ latestReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
2046
2518
  PARTITION BY ${posts.threadId}
2047
2519
  ORDER BY ${posts.createdAt} DESC, ${posts.id} DESC
2048
- )`.as("reply_rank"),
2049
- totalReplyCount: sql<number>`COUNT(*) OVER (
2520
+ ) AS INTEGER)`.as("latest_reply_rank"),
2521
+ totalReplyCount: sql<number>`CAST(COUNT(*) OVER (
2050
2522
  PARTITION BY ${posts.threadId}
2051
- )`.as("total_reply_count"),
2523
+ ) AS INTEGER)`.as("total_reply_count"),
2052
2524
  })
2053
2525
  .from(posts)
2054
2526
  .where(
@@ -2062,41 +2534,72 @@ export function createPostService(
2062
2534
  )
2063
2535
  .as("ranked_replies");
2064
2536
 
2065
- const latestReplyRows = await db
2537
+ const contextRows = await db
2066
2538
  .select({
2067
2539
  threadId: rankedReplies.threadId,
2068
- latestReplyId: rankedReplies.id,
2069
- latestReplyToId: rankedReplies.replyToId,
2540
+ id: rankedReplies.id,
2541
+ firstReplyRank: rankedReplies.firstReplyRank,
2542
+ latestReplyRank: rankedReplies.latestReplyRank,
2070
2543
  totalReplyCount: rankedReplies.totalReplyCount,
2071
2544
  })
2072
2545
  .from(rankedReplies)
2073
- .where(eq(rankedReplies.replyRank, 1));
2546
+ .where(
2547
+ or(
2548
+ eq(rankedReplies.firstReplyRank, 1),
2549
+ lte(rankedReplies.latestReplyRank, 2),
2550
+ ),
2551
+ );
2074
2552
 
2075
- const relatedPostIds = latestReplyRows.flatMap((row) => {
2076
- const ids = [row.latestReplyId];
2553
+ const hydratedPosts = await hydratePostsById(
2554
+ contextRows.map((row) => row.id),
2555
+ );
2077
2556
 
2078
- if (row.latestReplyToId && row.latestReplyToId !== row.threadId) {
2079
- ids.push(row.latestReplyToId);
2557
+ const contextByThreadId = new Map<
2558
+ string,
2559
+ {
2560
+ secondReplyId: string | null;
2561
+ penultimateReplyId: string | null;
2562
+ latestReplyId: string | null;
2563
+ totalReplyCount: number;
2080
2564
  }
2565
+ >();
2566
+ for (const row of contextRows) {
2567
+ const existing = contextByThreadId.get(row.threadId) ?? {
2568
+ secondReplyId: null,
2569
+ penultimateReplyId: null,
2570
+ latestReplyId: null,
2571
+ totalReplyCount: row.totalReplyCount,
2572
+ };
2081
2573
 
2082
- return ids;
2083
- });
2084
- const hydratedPosts = await hydratePostsById(relatedPostIds);
2574
+ if (row.firstReplyRank === 1) {
2575
+ existing.secondReplyId = row.id;
2576
+ }
2577
+ if (row.latestReplyRank === 2) {
2578
+ existing.penultimateReplyId = row.id;
2579
+ }
2580
+ if (row.latestReplyRank === 1) {
2581
+ existing.latestReplyId = row.id;
2582
+ }
2583
+
2584
+ contextByThreadId.set(row.threadId, existing);
2585
+ }
2085
2586
 
2086
2587
  const result = new Map<string, ThreadTimelineContext>();
2087
- for (const row of latestReplyRows) {
2088
- const latestReply = hydratedPosts.get(row.latestReplyId);
2089
- if (!latestReply) continue;
2588
+ for (const [threadId, context] of contextByThreadId) {
2589
+ if (!context.latestReplyId) continue;
2090
2590
 
2091
- const parentReply =
2092
- row.latestReplyToId && row.latestReplyToId !== row.threadId
2093
- ? (hydratedPosts.get(row.latestReplyToId) ?? null)
2094
- : null;
2591
+ const latestReply = hydratedPosts.get(context.latestReplyId);
2592
+ if (!latestReply) continue;
2095
2593
 
2096
- result.set(row.threadId, {
2594
+ result.set(threadId, {
2595
+ secondReply: context.secondReplyId
2596
+ ? (hydratedPosts.get(context.secondReplyId) ?? null)
2597
+ : null,
2598
+ penultimateReply: context.penultimateReplyId
2599
+ ? (hydratedPosts.get(context.penultimateReplyId) ?? null)
2600
+ : null,
2097
2601
  latestReply,
2098
- parentReply,
2099
- totalReplyCount: row.totalReplyCount,
2602
+ totalReplyCount: context.totalReplyCount,
2100
2603
  });
2101
2604
  }
2102
2605
 
@@ -2111,7 +2614,10 @@ export function createPostService(
2111
2614
 
2112
2615
  const rows = await db
2113
2616
  .select({
2114
- count: sql<number>`count(distinct ${posts.threadId})`.as("count"),
2617
+ count:
2618
+ sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
2619
+ "count",
2620
+ ),
2115
2621
  })
2116
2622
  .from(posts)
2117
2623
  .where(and(...conditions));
@@ -2150,14 +2656,27 @@ export function createPostService(
2150
2656
  },
2151
2657
 
2152
2658
  async countCollectionThreadRoots(collectionId, options = {}) {
2659
+ return this.countCollectionThreadRootsForCollections(
2660
+ [collectionId],
2661
+ options,
2662
+ );
2663
+ },
2664
+
2665
+ async countCollectionThreadRootsForCollections(
2666
+ collectionIds,
2667
+ options = {},
2668
+ ) {
2153
2669
  const conditions = [
2154
2670
  ...buildThreadRootPageConditions(options),
2155
- eq(postCollections.collectionId, collectionId),
2671
+ buildCollectionMembershipCondition(collectionIds),
2156
2672
  ];
2157
2673
 
2158
2674
  const rows = await db
2159
2675
  .select({
2160
- count: sql<number>`count(distinct ${posts.threadId})`.as("count"),
2676
+ count:
2677
+ sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
2678
+ "count",
2679
+ ),
2161
2680
  })
2162
2681
  .from(posts)
2163
2682
  .innerJoin(
@@ -2173,15 +2692,25 @@ export function createPostService(
2173
2692
  },
2174
2693
 
2175
2694
  async listCollectionThreadRootIds(collectionId, options = {}) {
2695
+ return this.listCollectionThreadRootIdsForCollections(
2696
+ [collectionId],
2697
+ options,
2698
+ );
2699
+ },
2700
+
2701
+ async listCollectionThreadRootIdsForCollections(
2702
+ collectionIds,
2703
+ options = {},
2704
+ ) {
2176
2705
  const conditions = [
2177
2706
  ...buildThreadRootPageConditions(options),
2178
- eq(postCollections.collectionId, collectionId),
2707
+ buildCollectionMembershipCondition(collectionIds),
2179
2708
  ];
2180
2709
  const sortOrder = options.sortOrder ?? "newest";
2181
- const collectedAt =
2710
+ const publishedAt =
2182
2711
  sortOrder === "oldest"
2183
- ? sql<number>`MIN(${postCollections.createdAt})`.as("collected_at")
2184
- : sql<number>`MAX(${postCollections.createdAt})`.as("collected_at");
2712
+ ? sql<number>`MIN(${posts.publishedAt})`.as("published_at")
2713
+ : sql<number>`MAX(${posts.publishedAt})`.as("published_at");
2185
2714
  const ratingPresence = sql<number>`MAX(
2186
2715
  CASE
2187
2716
  WHEN ${posts.rating} IS NULL THEN 0
@@ -2192,10 +2721,16 @@ export function createPostService(
2192
2721
  "rating_value",
2193
2722
  );
2194
2723
 
2724
+ const collectionPinnedAt =
2725
+ sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
2726
+ "collection_pinned_at",
2727
+ );
2728
+
2195
2729
  const baseQuery = db
2196
2730
  .select({
2197
2731
  threadId: posts.threadId,
2198
- collectedAt,
2732
+ publishedAt,
2733
+ collectionPinnedAt,
2199
2734
  ratingPresence,
2200
2735
  ratingValue,
2201
2736
  })
@@ -2212,15 +2747,24 @@ export function createPostService(
2212
2747
 
2213
2748
  let query =
2214
2749
  sortOrder === "oldest"
2215
- ? baseQuery.orderBy(asc(collectedAt), asc(posts.threadId))
2750
+ ? baseQuery.orderBy(
2751
+ desc(collectionPinnedAt),
2752
+ asc(publishedAt),
2753
+ asc(posts.threadId),
2754
+ )
2216
2755
  : sortOrder === "rating_desc"
2217
2756
  ? baseQuery.orderBy(
2757
+ desc(collectionPinnedAt),
2218
2758
  desc(ratingPresence),
2219
2759
  desc(ratingValue),
2220
- desc(collectedAt),
2760
+ desc(publishedAt),
2221
2761
  desc(posts.threadId),
2222
2762
  )
2223
- : baseQuery.orderBy(desc(collectedAt), desc(posts.threadId));
2763
+ : baseQuery.orderBy(
2764
+ desc(collectionPinnedAt),
2765
+ desc(publishedAt),
2766
+ desc(posts.threadId),
2767
+ );
2224
2768
 
2225
2769
  if (options.limit !== undefined) {
2226
2770
  query = query.limit(options.limit) as typeof query;
@@ -2234,18 +2778,34 @@ export function createPostService(
2234
2778
  },
2235
2779
 
2236
2780
  async listCollectionFeedEntries(collectionId, options = {}) {
2781
+ return this.listCollectionFeedEntriesForCollections(
2782
+ [collectionId],
2783
+ options,
2784
+ );
2785
+ },
2786
+
2787
+ async listCollectionFeedEntriesForCollections(collectionIds, options = {}) {
2237
2788
  const conditions = [
2238
2789
  ...buildThreadRootPageConditions(options),
2239
- eq(postCollections.collectionId, collectionId),
2790
+ buildCollectionMembershipCondition(collectionIds),
2240
2791
  ];
2792
+ const publishedAt = sql<number>`MAX(${posts.publishedAt})`.as(
2793
+ "published_at",
2794
+ );
2241
2795
  const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
2242
2796
  "collected_at",
2243
2797
  );
2798
+ const collectionPinnedAt =
2799
+ sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
2800
+ "collection_pinned_at",
2801
+ );
2244
2802
 
2245
2803
  let query = db
2246
2804
  .select({
2247
2805
  threadId: posts.threadId,
2806
+ publishedAt,
2248
2807
  collectedAt,
2808
+ collectionPinnedAt,
2249
2809
  })
2250
2810
  .from(posts)
2251
2811
  .innerJoin(
@@ -2257,7 +2817,11 @@ export function createPostService(
2257
2817
  )
2258
2818
  .where(and(...conditions))
2259
2819
  .groupBy(posts.threadId)
2260
- .orderBy(desc(collectedAt), desc(posts.threadId));
2820
+ .orderBy(
2821
+ desc(collectionPinnedAt),
2822
+ desc(publishedAt),
2823
+ desc(posts.threadId),
2824
+ );
2261
2825
 
2262
2826
  if (options.limit !== undefined) {
2263
2827
  query = query.limit(options.limit) as typeof query;
@@ -2306,6 +2870,13 @@ export function createPostService(
2306
2870
  },
2307
2871
 
2308
2872
  async getCollectionPostIdsByThread(collectionId, threadIds) {
2873
+ return this.getCollectionPostIdsByThreadForCollections(
2874
+ [collectionId],
2875
+ threadIds,
2876
+ );
2877
+ },
2878
+
2879
+ async getCollectionPostIdsByThreadForCollections(collectionIds, threadIds) {
2309
2880
  const result = new Map<string, string[]>();
2310
2881
  if (threadIds.length === 0) return result;
2311
2882
 
@@ -2327,12 +2898,13 @@ export function createPostService(
2327
2898
  .where(
2328
2899
  and(
2329
2900
  eq(posts.siteId, siteId),
2330
- eq(postCollections.collectionId, collectionId),
2901
+ buildCollectionMembershipCondition(collectionIds),
2331
2902
  inArray(posts.threadId, chunk),
2332
2903
  eq(posts.status, "published"),
2333
2904
  isNull(posts.deletedAt),
2334
2905
  ),
2335
2906
  )
2907
+ .groupBy(posts.threadId, posts.id)
2336
2908
  .orderBy(posts.threadId, posts.createdAt, posts.id),
2337
2909
  );
2338
2910