@jant/core 0.3.40 → 0.3.43

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 (1191) hide show
  1. package/README.md +21 -34
  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-reindex.js +175 -0
  10. package/bin/commands/search.js +53 -0
  11. package/bin/commands/settings.js +93 -0
  12. package/bin/commands/site/export.js +34 -33
  13. package/bin/commands/site/{localize-media.js → pull-media.js} +17 -17
  14. package/bin/lib/d1-query.js +15 -3
  15. package/bin/lib/http-api.js +223 -0
  16. package/bin/lib/hugo-markdown.js +102 -0
  17. package/bin/lib/media-upload.js +206 -0
  18. package/bin/lib/migration-runner.js +64 -0
  19. package/bin/lib/runtime-target.js +7 -2
  20. package/bin/lib/site-media-parser.js +71 -16
  21. package/bin/lib/site-pull-media.js +695 -0
  22. package/bin/lib/site-snapshot.js +0 -1
  23. package/dist/app-Ctl0T0zO.js +5 -0
  24. package/dist/{app-CAtsuLLh.js → app-GbfwoeDJ.js} +24357 -23139
  25. package/dist/client/.vite/manifest.json +3485 -0
  26. package/dist/client/_assets/chunks/02611a045a7fe83a12014e3debc9f731-BqEDtwXC.woff2 +0 -0
  27. package/dist/client/_assets/chunks/033466ef683afe931f7f520cfb42d928-BluLHEh9.woff2 +0 -0
  28. package/dist/client/_assets/chunks/04b8bde59cff68eeee74fb1914eb09c7-D5j2CLTt.woff2 +0 -0
  29. package/dist/client/_assets/chunks/057a6a98bda7fe57105ddaa99ec82015-DY_AoijT.woff2 +0 -0
  30. package/dist/client/_assets/chunks/05ac821472e235943ed1435e4bb8ecad-CUm8GiH6.woff2 +0 -0
  31. package/dist/client/_assets/chunks/05da12edb9d52210581dc6ec4541031f-BXAOx2XP.woff2 +0 -0
  32. package/dist/client/_assets/chunks/074c1f8483d5a4d8c45c8c5f3e4cbb32-CDqdMQm8.woff2 +0 -0
  33. package/dist/client/_assets/chunks/0968e4861204b51f62a2f8e9f15dd5e0-D5tLgF_8.woff2 +0 -0
  34. package/dist/client/_assets/chunks/0acd1fe2b2ea1ad1bfee7ae1fa139d27-BKK1m412.woff2 +0 -0
  35. package/dist/client/_assets/chunks/0c2ab6b295e55f356f8020d4e7747522-CkCQjQw3.woff2 +0 -0
  36. package/dist/client/_assets/chunks/0c5f9492af03a4fa42c784de94649de1-CQlJqksa.woff2 +0 -0
  37. package/dist/client/_assets/chunks/0d5dec931dc885f07fe5cd5af8bed675-C74x-U_U.woff2 +0 -0
  38. package/dist/client/_assets/chunks/0d9a936885a4c39077438effd3779cbd-Bw0fYF4S.woff2 +0 -0
  39. package/dist/client/_assets/chunks/0e645da524f7cfc0e8c3c03fb2b08428-BVeI_5Th.woff2 +0 -0
  40. package/dist/client/_assets/chunks/0f5e1a18987dbc84ca05188c129e1936-B6_lZ6fE.woff2 +0 -0
  41. package/dist/client/_assets/chunks/1076f0f6f66d28d7a2f16427faad4413-ubiXnD1P.woff2 +0 -0
  42. package/dist/client/_assets/chunks/112743a4ab5fdd1498dfdf2b11336380-B5FzQxTK.woff2 +0 -0
  43. package/dist/client/_assets/chunks/1211f03d3ff5759a702631445793f60e-DDcSiyCt.woff2 +0 -0
  44. package/dist/client/_assets/chunks/12ef1ba76bd20b004b865266a1aa70b3-BwmaD87S.woff2 +0 -0
  45. package/dist/client/_assets/chunks/135e83b403475c5dc9e49b43501a5b84-C5mE_KZS.woff2 +0 -0
  46. package/dist/client/_assets/chunks/1380210a722ac9aeef54005ad7b015c9-CjA-MP82.woff2 +0 -0
  47. package/dist/client/_assets/chunks/13b2c53b30e6a3e4de7132dbc18dbdfc-B9WOB7kv.woff2 +0 -0
  48. package/dist/client/_assets/chunks/147b24c2f08d03bbed30887d4cb3311c-IeeFCJ13.woff2 +0 -0
  49. package/dist/client/_assets/chunks/152395634a207579552f8cb54db88599-C2y2JyNZ.woff2 +0 -0
  50. package/dist/client/_assets/chunks/15d95680dc31cc6ce20e0a5464106dc4-CpD8dqRm.woff2 +0 -0
  51. package/dist/client/_assets/chunks/16663e567f81f4725a1522f37e18f71f-BNoxS9pQ.woff2 +0 -0
  52. package/dist/client/_assets/chunks/1733f27476507ca68b68a803bc533cc2-DXL-14Ug.woff2 +0 -0
  53. package/dist/client/_assets/chunks/17c7f5d0a45e92ede0e5dec3a2c77efa-CNj3jQMl.woff2 +0 -0
  54. package/dist/client/_assets/chunks/1824321da801e6257b902f3d1d09054c-D4dgdyWB.woff2 +0 -0
  55. package/dist/client/_assets/chunks/188c2db794f3dd7a45889ddbc81da9bb-C9NbUBBO.woff2 +0 -0
  56. package/dist/client/_assets/chunks/1a08931435f885e707edb85978ea3bb6-BOiP2u7G.woff2 +0 -0
  57. package/dist/client/_assets/chunks/1a3a92c7c060a71a6c35819b9ebcdbb9-DFfonQfz.woff2 +0 -0
  58. package/dist/client/_assets/chunks/1be602ad456b0d75902d352116cb35fd-Bc-XQtQh.woff2 +0 -0
  59. package/dist/client/_assets/chunks/1cf737900dd49c2e88f1b3221a82a602-CfTmnns3.woff2 +0 -0
  60. package/dist/client/_assets/chunks/1fba7ec0e412e911bf31841de5a8a4e4-18ELMbFF.woff2 +0 -0
  61. package/dist/client/_assets/chunks/233d3a685ee18276b319363474599d47-BaFdJ0-G.woff2 +0 -0
  62. package/dist/client/_assets/chunks/2612c60f9dc5459ac42763591240a1d6-CTD69cq1.woff2 +0 -0
  63. package/dist/client/_assets/chunks/284b53bbefb06924cf236d24c8ed5641-CPDiuOv6.woff2 +0 -0
  64. package/dist/client/_assets/chunks/28a97af9ab9d38983d20f39ff09840e1-mFqcT3Jw.woff2 +0 -0
  65. package/dist/client/_assets/chunks/2a56eaf19d1d38a6b57e2a388f733676-CU52qBlE.woff2 +0 -0
  66. package/dist/client/_assets/chunks/2ab8cf1f23a5ac7939a7876054e711a7-B4T3dK7A.woff2 +0 -0
  67. package/dist/client/_assets/chunks/2abfbab82b6a7c04426afc054d2464da-D5PIIHde.woff2 +0 -0
  68. package/dist/client/_assets/chunks/2bfaadaf3479c72286248e6de0be0ec9-DEpU6O74.woff2 +0 -0
  69. package/dist/client/_assets/chunks/2c8c55e4cec85ce77e95cac9d330f5cf-BCTA96EZ.woff2 +0 -0
  70. package/dist/client/_assets/chunks/2e6f4bb71ef6b38765d51acc5a79f638-Mbs6PECD.woff2 +0 -0
  71. package/dist/client/_assets/chunks/2ed9981d2e8983365bd051159b976b6d-D8gg6NWM.woff2 +0 -0
  72. package/dist/client/_assets/chunks/2f4c633e923ba30c6ba376367379fc91-CIJCFztv.woff2 +0 -0
  73. package/dist/client/_assets/chunks/31194d303a67561926a544ed0e072aee-w3-eGYAi.woff2 +0 -0
  74. package/dist/client/_assets/chunks/31a197213ae61d7043c81013f52d10d6-DXZrrkJP.woff2 +0 -0
  75. package/dist/client/_assets/chunks/349965eee0a9b6c984a319ab96a4ece9-DByAVmXw.woff2 +0 -0
  76. package/dist/client/_assets/chunks/34e6750e00c3a911ef87dc66b336594d-DNbWesOu.woff2 +0 -0
  77. package/dist/client/_assets/chunks/3623d96698132c61e861f984ce11cc23-jqV9ErzX.woff2 +0 -0
  78. package/dist/client/_assets/chunks/36cda3eae13370b934bba4429fab9078-BPDcUlw9.woff2 +0 -0
  79. package/dist/client/_assets/chunks/36f3df4730cfca4fc77fe52b52e6a126-BwK10NC0.woff2 +0 -0
  80. package/dist/client/_assets/chunks/37887deb7a9c466cf2af6ee7ff372ea6-BrAerJB1.woff2 +0 -0
  81. package/dist/client/_assets/chunks/37dbd564820cce2f7b3331e442da0df3-Cql2kzIi.woff2 +0 -0
  82. package/dist/client/_assets/chunks/37dc8505b20b37a2477a9121b16d44a7-CoXw6XWV.woff2 +0 -0
  83. package/dist/client/_assets/chunks/39211b02f24c69cbd1b54f6d41b01933-4Xu7vvnq.woff2 +0 -0
  84. package/dist/client/_assets/chunks/3aa13f1843d7c8fa16109b6395a9a29d-BQFZUc3B.woff2 +0 -0
  85. package/dist/client/_assets/chunks/3aba89c4d4281553078de4622b498cbb-8QS1zDYk.woff2 +0 -0
  86. package/dist/client/_assets/chunks/3ac0a07878cacdc98bb889f40f5970d3-C-BaM6-y.woff2 +0 -0
  87. package/dist/client/_assets/chunks/3d88558097559d775231194d43745ba7-CR2aqGC7.woff2 +0 -0
  88. package/dist/client/_assets/chunks/3dc3e8c35524cad2d1236f3393104ccf-9sqBsF7B.woff2 +0 -0
  89. package/dist/client/_assets/chunks/3e24063b19abd2b051af986a6870046d-TlweM3SW.woff2 +0 -0
  90. package/dist/client/_assets/chunks/3e2c06bdd9dcab29aeaeb0cfb7fa608a-BaqU-dIt.woff2 +0 -0
  91. package/dist/client/_assets/chunks/424123f9371ba357087363263eeafcda-C8SVfEJi.woff2 +0 -0
  92. package/dist/client/_assets/chunks/43b3bbf43055c3e65db9cdf2c5b18c1c-CbHx6-Ki.woff2 +0 -0
  93. package/dist/client/_assets/chunks/4410f876d27c9821839474b6b1059fd7-ClYz91aY.woff2 +0 -0
  94. package/dist/client/_assets/chunks/44704471b60c18b3ecec5300a88f1102-DDmmy6-m.woff2 +0 -0
  95. package/dist/client/_assets/chunks/45140742dfa8686b58db2899ce89f89a-D5IMMzDM.woff2 +0 -0
  96. package/dist/client/_assets/chunks/46604090efbb99c2db1800928af0a239-JX2fpYX_.woff2 +0 -0
  97. package/dist/client/_assets/chunks/46998df85d31e629c0142f0556f5e7c5-BIkNG9Wh.woff2 +0 -0
  98. package/dist/client/_assets/chunks/469d2754e315e77270c12fa0ab28026c-D-y-vsfA.woff2 +0 -0
  99. package/dist/client/_assets/chunks/479329a39a3eb6c0047e6b0981919855-vZKCRx42.woff2 +0 -0
  100. package/dist/client/_assets/chunks/491bbba374093fff045f55803a22bfef-DSQXiDCd.woff2 +0 -0
  101. package/dist/client/_assets/chunks/49902f1c41b281f8eb3771abdd4dccb8-DxVxNg2W.woff2 +0 -0
  102. package/dist/client/_assets/chunks/4c0760284569b87d8f2290c324953973-Bqd-TKHH.woff2 +0 -0
  103. package/dist/client/_assets/chunks/4cba92c942694bdc479bc54101bb1aa4-BrfsrgrC.woff2 +0 -0
  104. package/dist/client/_assets/chunks/4d38e56738156625329d93bd5c8cc74a-DONkMP0y.woff2 +0 -0
  105. package/dist/client/_assets/chunks/4db4f31a16965baa42cb7dad667a8f04-Dt1Cy4gr.woff2 +0 -0
  106. package/dist/client/_assets/chunks/4e7d263c30bb48604069a3b4404d4eeb-DNPCkvKD.woff2 +0 -0
  107. package/dist/client/_assets/chunks/5279c7e465d9871edf51fd02e691eee7-CKhyhvVf.woff2 +0 -0
  108. package/dist/client/_assets/chunks/527ff336279465617aafbafe1a830632-CC3lv4pm.woff2 +0 -0
  109. package/dist/client/_assets/chunks/54bfdb6f21f9d6191e5100580239e14f-PGlqYvgl.woff2 +0 -0
  110. package/dist/client/_assets/chunks/56b40518ea0608e62826bd44ee27f5f9-BD9F9Oss.woff2 +0 -0
  111. package/dist/client/_assets/chunks/56be5e32fd01668fe5ba814d94905839-CSyFA-9l.woff2 +0 -0
  112. package/dist/client/_assets/chunks/572b9e28f1e6d89b765a16d809db84a1-BIYMjzEa.woff2 +0 -0
  113. package/dist/client/_assets/chunks/57e9a86651366c1ba299e47aa7e358c2-BHuZXB4W.woff2 +0 -0
  114. package/dist/client/_assets/chunks/57f89adeae55aa7fe2add3fc1801ce03-Cy3PNrfx.woff2 +0 -0
  115. package/dist/client/_assets/chunks/5921cef36750ff6092d94b83a802823b-hF-uymTh.woff2 +0 -0
  116. package/dist/client/_assets/chunks/59e95d8b5332dcdae894a19946ab767d-ClRfsjWZ.woff2 +0 -0
  117. package/dist/client/_assets/chunks/5acc7f736217259db79a42bf44241c48-FYQUuFOM.woff2 +0 -0
  118. package/dist/client/_assets/chunks/5c05f8b75c9d7f25b3c04ca711cf43b3-D-YWPkSq.woff2 +0 -0
  119. package/dist/client/_assets/chunks/5df9ce98c75f8c50fb4fff7f01a23925-DgoCENyW.woff2 +0 -0
  120. package/dist/client/_assets/chunks/5e0124c7265f1b4cf0fc797b94efbebc-BTBNHsSB.woff2 +0 -0
  121. package/dist/client/_assets/chunks/5ec349ff62b9ee5fe0c6dae867704dc4-B4dezhrz.woff2 +0 -0
  122. package/dist/client/_assets/chunks/60020a830d809c26970cf8e48ba1eeda-B6FDTSik.woff2 +0 -0
  123. package/dist/client/_assets/chunks/601f812e8b1eafecd5ba585a6a6d7962-gIVBlUU9.woff2 +0 -0
  124. package/dist/client/_assets/chunks/6025ddaf99d86cc8bfd781006b19fbd6-CS-ze337.woff2 +0 -0
  125. package/dist/client/_assets/chunks/60bcfa3ea7446eb42394aab02d28be40-C-PQfCu3.woff2 +0 -0
  126. package/dist/client/_assets/chunks/60f6e28b3f0400b0e8fe32c3c7116102-Dag9L2qU.woff2 +0 -0
  127. package/dist/client/_assets/chunks/615a7e0d63f257109d599b2a1977de68-D6Y93yoG.woff2 +0 -0
  128. package/dist/client/_assets/chunks/615b38e0b5bb3ea68426f44f47706e6f-B5yzIIer.woff2 +0 -0
  129. package/dist/client/_assets/chunks/61a2c80d0c924d5a6187c02e8d1d1642-BY3MmHbZ.woff2 +0 -0
  130. package/dist/client/_assets/chunks/62fc94f8d4c5a750b7f25e7387539910-3ql2_WDK.woff2 +0 -0
  131. package/dist/client/_assets/chunks/639c7c6b0b0c27c738702741cfa4b8c0-BCD3OGri.woff2 +0 -0
  132. package/dist/client/_assets/chunks/63fafcb069520613d0ea35ad3e6b1e42-DP0Rrj4o.woff2 +0 -0
  133. package/dist/client/_assets/chunks/64a404d675f1d726f0891b7a80794595-DU3L0I9s.woff2 +0 -0
  134. package/dist/client/_assets/chunks/65a60d87c64228d258a123cbe85f5f31-B4kbf-79.woff2 +0 -0
  135. package/dist/client/_assets/chunks/65e9c4585d71bf48a5c62f367010da16-CvzgzReN.woff2 +0 -0
  136. package/dist/client/_assets/chunks/68a3a4bf337f8a0722be76676e20b850-Dnaw8EPD.woff2 +0 -0
  137. package/dist/client/_assets/chunks/6968e889e18891d912809fe484c2e745-Bj1m5e0s.woff2 +0 -0
  138. package/dist/client/_assets/chunks/6abdd163d2c4b85698db8aa1ce149361-B5FVGJuo.woff2 +0 -0
  139. package/dist/client/_assets/chunks/6b75213eb0be40ce84241eb2bb438a2e-CqHmF-NR.woff2 +0 -0
  140. package/dist/client/_assets/chunks/70714891ad3fbfc3d5f10a8669dacc5a-BWk5nLFN.woff2 +0 -0
  141. package/dist/client/_assets/chunks/7158022889c6c177062ac85036e7af10-B3D4K22n.woff2 +0 -0
  142. package/dist/client/_assets/chunks/73b794d885b88f7befb7ea8ab1780a15-D0ZehnEI.woff2 +0 -0
  143. package/dist/client/_assets/chunks/743f290baf027d4626a86e22f3d44600-DDTDQdRC.woff2 +0 -0
  144. package/dist/client/_assets/chunks/7520ca6ca8c60eb1e62d50e71c8d30f1-CVRaGu2G.woff2 +0 -0
  145. package/dist/client/_assets/chunks/757019c01c5e2c2b0f54293cea3b5636-BTovUnoH.woff2 +0 -0
  146. package/dist/client/_assets/chunks/759a647b77791b1e98f99bc0ab5317a7-CrK6FIFz.woff2 +0 -0
  147. package/dist/client/_assets/chunks/761d84afca8d7f34eebefe538adba827-DRbiVuwX.woff2 +0 -0
  148. package/dist/client/_assets/chunks/76e07530323418ec723c5d7634c9bcca-DGtVJTTH.woff2 +0 -0
  149. package/dist/client/_assets/chunks/76e51630143b95b6322ef93ad330da7a-D-kV82C_.woff2 +0 -0
  150. package/dist/client/_assets/chunks/76f117a3baacda304a71965a17266a13-DFfvsFUm.woff2 +0 -0
  151. package/dist/client/_assets/chunks/773819b7b9b8fd404a929867c0fd677e-Bu1xCMZb.woff2 +0 -0
  152. package/dist/client/_assets/chunks/77747f17625a259fb405b7bbdd84ac4b-o_vNfh-6.woff2 +0 -0
  153. package/dist/client/_assets/chunks/7817dd16805145d8538ad57590f69f5a-DguSUP3O.woff2 +0 -0
  154. package/dist/client/_assets/chunks/788498548ddfb710d6ef3c7bff79fa97-3ffoQfRd.woff2 +0 -0
  155. package/dist/client/_assets/chunks/78ea6c40923ce95367e3517dd1e5a849-DvLDRhX9.woff2 +0 -0
  156. package/dist/client/_assets/chunks/79aa7c8c842c4a27cf57c0a3a1ffca5a-sDoCeoKI.woff2 +0 -0
  157. package/dist/client/_assets/chunks/7b59f0ec7792b18458dc5a361e37884c-DWvxkKmT.woff2 +0 -0
  158. package/dist/client/_assets/chunks/7c3945788a689a69356c1a622d69d48b-1JjIpss2.woff2 +0 -0
  159. package/dist/client/_assets/chunks/7cc72fd2c9105560422b6a67c6162945-Co3AF5_G.woff2 +0 -0
  160. package/dist/client/_assets/chunks/7d0ea0690e432462f4d05a23d720ec19-Dx7DK4C3.woff2 +0 -0
  161. package/dist/client/_assets/chunks/7dffa5c1bec57e0903fd62357401ff1a-DuQHmiiN.woff2 +0 -0
  162. package/dist/client/_assets/chunks/7e38ca789a9e76ac91d9256374e154f0-BtETgGRe.woff2 +0 -0
  163. package/dist/client/_assets/chunks/7f9a6c9286b68de9c72b0024f7beeb40-JpXdo4FC.woff2 +0 -0
  164. package/dist/client/_assets/chunks/815a0b797465190f60d8b1a04656c42e-Spzo94nU.woff2 +0 -0
  165. package/dist/client/_assets/chunks/823d1bb11097331238d103c7f72138dc-CGN6m2VF.woff2 +0 -0
  166. package/dist/client/_assets/chunks/82f6ccc063960eed1cdfd1d61ada8862-CsmAqx2a.woff2 +0 -0
  167. package/dist/client/_assets/chunks/835b6505bb9eea9678925a1fa885353d-C6mSBwUc.woff2 +0 -0
  168. package/dist/client/_assets/chunks/845b4b67564d62cf5cad242f7ac08ea5-DnvzrloJ.woff2 +0 -0
  169. package/dist/client/_assets/chunks/84c8e7bc0931008ed91f44ed12dfea94-C2Hvhh-b.woff2 +0 -0
  170. package/dist/client/_assets/chunks/8726558cf297cbda831cc19514897205-gSFV1vW0.woff2 +0 -0
  171. package/dist/client/_assets/chunks/8855472f3c02f6c7ebb3216617ebe4af-C1JsWVqq.woff2 +0 -0
  172. package/dist/client/_assets/chunks/887a11290ba78b1e66c6d2f67043005e-L_A1f5Fz.woff2 +0 -0
  173. package/dist/client/_assets/chunks/888ba53510213d5d1f6a7deb60e569b6-OP8E-ELN.woff2 +0 -0
  174. package/dist/client/_assets/chunks/88db1d042074fb6e66821ffc10941930-kZ6K3e62.woff2 +0 -0
  175. package/dist/client/_assets/chunks/892aa49b2529c89bb4076d4aa51fe30f-Bw0_Km2A.woff2 +0 -0
  176. package/dist/client/_assets/chunks/8b5c9ef81159f31d2ab35f45ca4373e0-Djw95sKC.woff2 +0 -0
  177. package/dist/client/_assets/chunks/8b91c7c2ed390f1278b9befa3aa87233-B2C3iFbQ.woff2 +0 -0
  178. package/dist/client/_assets/chunks/8dc18c0cebe6aa4bf4c45dbb831048ab-bUlANLit.woff2 +0 -0
  179. package/dist/client/_assets/chunks/8e231d707f0c4f8e3cde90a6b52a79aa-CvwbWlul.woff2 +0 -0
  180. package/dist/client/_assets/chunks/8fb45117a62d92dce44a80f0b729ead5-Djr4-o5-.woff2 +0 -0
  181. package/dist/client/_assets/chunks/9064c12fd72c7aba235d8c3881755b91-BnguxT-p.woff2 +0 -0
  182. package/dist/client/_assets/chunks/90b7848e9b1623b77bdcf155e90b839c-CFvPRnoM.woff2 +0 -0
  183. package/dist/client/_assets/chunks/911b9e53e9814de2998c60bf3115f560-DmPmj635.woff2 +0 -0
  184. package/dist/client/_assets/chunks/919a879bd2d580d8491a31a449390689-BY2w-8_Y.woff2 +0 -0
  185. package/dist/client/_assets/chunks/91da6cb174bebfb96976e2f1316be2fe-DztAJb6W.woff2 +0 -0
  186. package/dist/client/_assets/chunks/92fdc376bce277874e75db666f175b44-DJUnHEIN.woff2 +0 -0
  187. package/dist/client/_assets/chunks/930a23430f0eb64480d7fe5f82834e21-2VrDPT7E.woff2 +0 -0
  188. package/dist/client/_assets/chunks/934dfdbed2bb2c4b6129199d699a34fa-CtpHsjRD.woff2 +0 -0
  189. package/dist/client/_assets/chunks/93bed0e5f2dd5a21cf73304fcfbc149d-DJiaETX7.woff2 +0 -0
  190. package/dist/client/_assets/chunks/93f3ea6918533d96d9c252378b9a4af0-D-FJaGi-.woff2 +0 -0
  191. package/dist/client/_assets/chunks/95a0951d2a2722ff773a1a45e8c474d6-DuZg-BpP.woff2 +0 -0
  192. package/dist/client/_assets/chunks/95ff41191c76ef893ac70a1043e95e44-C08QDbJO.woff2 +0 -0
  193. package/dist/client/_assets/chunks/96c75862c8cec51a7c05ff025fea86cd-DBnRwCuW.woff2 +0 -0
  194. package/dist/client/_assets/chunks/97d7a17234f2b5030ae9697ae00aded2-CkUSaB-p.woff2 +0 -0
  195. package/dist/client/_assets/chunks/982cd1765ca6bbb3e6547e47834d5148-DkIybx1r.woff2 +0 -0
  196. package/dist/client/_assets/chunks/985d4d41afa0934a4eb2de35473fb9a2-CF7rgJMw.woff2 +0 -0
  197. package/dist/client/_assets/chunks/995c4fda62d25c3b79dd5987737df6ae-CY5ri8HW.woff2 +0 -0
  198. package/dist/client/_assets/chunks/9b0c3caac458c7bc9c9133e40405ddea-DhPrYOcz.woff2 +0 -0
  199. package/dist/client/_assets/chunks/9b21e8244f5930c48ad5073f83777b6c-CuE9-r-r.woff2 +0 -0
  200. package/dist/client/_assets/chunks/9bb38fd05201de666b88b82d56a386f7-nMl6lB8X.woff2 +0 -0
  201. package/dist/client/_assets/chunks/9d0afa53dc2f92ba42024f55f11f6779-Bozpx8bL.woff2 +0 -0
  202. package/dist/client/_assets/chunks/9e13a47242926f1cd7d68c08d9ef8889-DwKQjc8M.woff2 +0 -0
  203. package/dist/client/_assets/chunks/9eff2ab2a9d2ced47ab761bf6fdb11ec-D86-Zm1H.woff2 +0 -0
  204. package/dist/client/_assets/chunks/9f1963fe8d4d6878d717d872a8f3fdb5-DxjITUFq.woff2 +0 -0
  205. package/dist/client/_assets/chunks/9f3bdab4025dc7c5ba1a5871e62e8918-BqxDEFLJ.woff2 +0 -0
  206. package/dist/client/_assets/chunks/9fce02d1a09f464c36c9fcfb14a354c5-F2tMAB_j.woff2 +0 -0
  207. package/dist/client/_assets/chunks/a00e94d4f04df6aa659db9c4954c7efe-CRBKCj_3.woff2 +0 -0
  208. package/dist/client/_assets/chunks/a0a6755c7e3f9e4cf03387027dd9f16c-BAyRqF2w.woff2 +0 -0
  209. package/dist/client/_assets/chunks/a0d00fe4816c95a8c7dffd445ef00b03-CxcTiRLJ.woff2 +0 -0
  210. package/dist/client/_assets/chunks/a0f199077fa1a33bc2a1e01c64de7fe6-IdgVJmZD.woff2 +0 -0
  211. package/dist/client/_assets/chunks/a2b0597837e382aca19919c4b74e58c8-7iVOBjzI.woff2 +0 -0
  212. package/dist/client/_assets/chunks/a373e85c96aa0dc303092429fb837e51-Dvmw8mbX.woff2 +0 -0
  213. package/dist/client/_assets/chunks/a379ada89abd750c587ded29f77e731c-CgPBsEsv.woff2 +0 -0
  214. package/dist/client/_assets/chunks/a4475c442c6b259354c9eda62e64e7df-TZ0c8vK-.woff2 +0 -0
  215. package/dist/client/_assets/chunks/a48be56ce5d3a99dc8f8331398004412-BR_ixH1W.woff2 +0 -0
  216. package/dist/client/_assets/chunks/a4a39b5f048671aa37c2b2a9581ab08a-EDfDYsUd.woff2 +0 -0
  217. package/dist/client/_assets/chunks/a534449d67561ac9b06489f64e57ad98-C355TCrD.woff2 +0 -0
  218. package/dist/client/_assets/chunks/a6e044424780a57610833cc856c15bf5-x1b8FE4H.woff2 +0 -0
  219. package/dist/client/_assets/chunks/a7c35e42a659347e490c3cb7983b2e7f-BWpA0-oz.woff2 +0 -0
  220. package/dist/client/_assets/chunks/a7c58070d68dc1724e9d4e781afb78db-ql27pT-6.woff2 +0 -0
  221. package/dist/client/_assets/chunks/a806759e72987951d6d08b7cbdf36a1b-TvLDwB_u.woff2 +0 -0
  222. package/dist/client/_assets/chunks/a88481ec8bc7be11cb66e46781a79bb9-Ck-r_sGx.woff2 +0 -0
  223. package/dist/client/_assets/chunks/a919a8c6fec6d7e8bb21177965f2e9d7-CC5uWzwd.woff2 +0 -0
  224. package/dist/client/_assets/chunks/a9a016d4a93409f65278327e8a8bb38d-dylrNwF8.woff2 +0 -0
  225. package/dist/client/_assets/chunks/aa662092cf249707123ca4d575e2764b-CSxNsTUL.woff2 +0 -0
  226. package/dist/client/_assets/chunks/aae1eda193285ab817a2d1b408440326-CjyjBoRm.woff2 +0 -0
  227. package/dist/client/_assets/chunks/ab92d41062a7644faa45f50bf384454a-Cq0X8a9H.woff2 +0 -0
  228. package/dist/client/_assets/chunks/accc43762ad05edfbff66ba1380d192a-B5jIykaZ.woff2 +0 -0
  229. package/dist/client/_assets/chunks/ad47bcd6d4663026d648c132d969318d-BYdIbDge.woff2 +0 -0
  230. package/dist/client/_assets/chunks/ad67113f88b59582991cb0c5d33ea19f-DOH1vcIZ.woff2 +0 -0
  231. package/dist/client/_assets/chunks/adb6e613fb99c6dd660b727101f554a8-DJDw_ZZK.woff2 +0 -0
  232. package/dist/client/_assets/chunks/adc4a98a89870ef984ee8f4b57c8a3a5-D30vw26R.woff2 +0 -0
  233. package/dist/client/_assets/chunks/af57a2e8c72f9ba0efb1af771d32c124-D5Zwq9pS.woff2 +0 -0
  234. package/dist/client/_assets/chunks/b0ae5f374e43dac992a4a5d334cebc0b-Ccoi4dPt.woff2 +0 -0
  235. package/dist/client/_assets/chunks/b0cb664cb2e1371efda8943c0b7dcd1c-YzGFnelF.woff2 +0 -0
  236. package/dist/client/_assets/chunks/b0d1cdced482352cf0d3ae58638aacb9-IunWfc6R.woff2 +0 -0
  237. package/dist/client/_assets/chunks/b12379ab782468d725519cd07a7d15bc-IjbydKs2.woff2 +0 -0
  238. package/dist/client/_assets/chunks/b320f5d185b2cff933ac549c184031c5-IYnPrIz4.woff2 +0 -0
  239. package/dist/client/_assets/chunks/b326a8a9c33b554db570da94f60bc380-B6dzaCQg.woff2 +0 -0
  240. package/dist/client/_assets/chunks/b55486f8a459c838fe329d4e79a8c211-DW8q7oGV.woff2 +0 -0
  241. package/dist/client/_assets/chunks/b6117ff2993b11bb1fdc7ea3588a010c-JoMYL5W_.woff2 +0 -0
  242. package/dist/client/_assets/chunks/b61982951bd51b724143c30dfaaa9fe9-D3KhBKl6.woff2 +0 -0
  243. package/dist/client/_assets/chunks/b91234c10fd8b8c8abc88e03afe66a1f-BABNh2la.woff2 +0 -0
  244. package/dist/client/_assets/chunks/b9317198f118a1dfd8ddf2b82ec028f3-CiMecxeH.woff2 +0 -0
  245. package/dist/client/_assets/chunks/ba825ae79b1a6f7e0cce5215fcb5c96f-B6EWU_D5.woff2 +0 -0
  246. package/dist/client/_assets/chunks/ba9c59d7dfa4494db1bb764ada81467d-D0yPcC1m.woff2 +0 -0
  247. package/dist/client/_assets/chunks/bab547459d514f46206e340c4bb2dc88-YnBqlGhw.woff2 +0 -0
  248. package/dist/client/_assets/chunks/bb0b15492b8cdbbec57c0bcfa0aa9241-BjIFbEsN.woff2 +0 -0
  249. package/dist/client/_assets/chunks/bc6183ac08f0fac78c46f80c10cf7c92-B81nbRb3.woff2 +0 -0
  250. package/dist/client/_assets/chunks/bc8ccea5abf6598cf3cfa97eb59804bb-B_7IbluG.woff2 +0 -0
  251. package/dist/client/_assets/chunks/bc95a792cbca6639214c9b0da13392ff-CHQe0E-D.woff2 +0 -0
  252. package/dist/client/_assets/chunks/bcc39eda837bb7a7a3d37c8c60fffb81-CGd1Zk6S.woff2 +0 -0
  253. package/dist/client/_assets/chunks/bcfa62f35731856246c146d3a6932bf3-DMDq6w4v.woff2 +0 -0
  254. package/dist/client/_assets/chunks/bd73264d7f98776708d5d6f3c9b78fcc-d9QWWMym.woff2 +0 -0
  255. package/dist/client/_assets/chunks/bed610b217d500f5975cfc9fe6157570-B5t7s1bw.woff2 +0 -0
  256. package/dist/client/_assets/chunks/bedc74b423b7293b6ad0bdecc61c42cc-4Nnh7peP.woff2 +0 -0
  257. package/dist/client/_assets/chunks/bf8901f8f11d4f433ea17dabc9370ea6-B8dkfvGn.woff2 +0 -0
  258. package/dist/client/_assets/chunks/bfb00d4a4c48661bd0be99f300a0faae-OxsWZ7l-.woff2 +0 -0
  259. package/dist/client/_assets/chunks/c0098958e20db68cab90097b5e62516f-VpjQo3vV.woff2 +0 -0
  260. package/dist/client/_assets/chunks/c063897793f593eb26d6ff7b7baaba18-BWeJJ1sH.woff2 +0 -0
  261. package/dist/client/_assets/chunks/c1b0df29ae41d764904df84e9ac83d1e-D1N934vs.woff2 +0 -0
  262. package/dist/client/_assets/chunks/c4143bb9f2fe77b6ccf20088a8904650-nHfYM24e.woff2 +0 -0
  263. package/dist/client/_assets/chunks/c42c67070cfe99cf823df92d81c7fa6e-B_a405Ez.woff2 +0 -0
  264. package/dist/client/_assets/chunks/c4bfaa5e50798246e3770718b7a7c84a-B-r6Y_o0.woff2 +0 -0
  265. package/dist/client/_assets/chunks/c4d749e45ecd5a5aed5a0bb3ebfd355d-CkqE1fRi.woff2 +0 -0
  266. package/dist/client/_assets/chunks/c6c2971ad1f3221f6cf84028aa0f477e-D7BZGIcY.woff2 +0 -0
  267. package/dist/client/_assets/chunks/c7ff3f6bbdcd5f604b7343602ab904df-Bq7UjVrZ.woff2 +0 -0
  268. package/dist/client/_assets/chunks/c84598999133455503042e06f4ab79cb-DIXndJX3.woff2 +0 -0
  269. package/dist/client/_assets/chunks/c945c62368357d05a53206620460fb30-82gFEf6n.woff2 +0 -0
  270. package/dist/client/_assets/chunks/c96d83978add28b356c22c4c84916733-BL4VaPIE.woff2 +0 -0
  271. package/dist/client/_assets/chunks/c97f41eef722121d86f55d553c056a39-CjejgQO2.woff2 +0 -0
  272. package/dist/client/_assets/chunks/ca62704509932d3232d62918de97af3f-CKWF-3mX.woff2 +0 -0
  273. package/dist/client/_assets/chunks/ca9a533988d7019597a60d4e17127e0c-ClZ6ygZM.woff2 +0 -0
  274. package/dist/client/_assets/chunks/cb7ccd6494256f7a2977b7c2b0225592-Cxp8XObm.woff2 +0 -0
  275. package/dist/client/_assets/chunks/ce022e18a1377ac509443c3c3790b431-DpH0t6Tn.woff2 +0 -0
  276. package/dist/client/_assets/chunks/ce41d70ce6a069a498525c9e15c45cf2-DGtgv-_b.woff2 +0 -0
  277. package/dist/client/_assets/chunks/cf2b28f90f47276f7e2688a65e88a101-LVbNkXHj.woff2 +0 -0
  278. package/dist/client/_assets/chunks/cf9cffe56636322f62b40d61130fbc5e-DNCBMqAo.woff2 +0 -0
  279. package/dist/client/_assets/chunks/d1f064825fa5784b5c930652bd831cce-BU6qwjp8.woff2 +0 -0
  280. package/dist/client/_assets/chunks/d20d8944bc0b85f5b2aae4b24f343516-oY9l-NmC.woff2 +0 -0
  281. package/dist/client/_assets/chunks/d2faabcedd19f016e7b21bce073c0ec8-Dv6eFCf5.woff2 +0 -0
  282. package/dist/client/_assets/chunks/d320171e57480510f87dbbc7d5264b0a-CcCfjaFX.woff2 +0 -0
  283. package/dist/client/_assets/chunks/d36644d6502527e1fff205d0c7eca434-kVBj8zy_.woff2 +0 -0
  284. package/dist/client/_assets/chunks/d3a72a99d365dddfbca8d017a8011368-Bgdmhl3M.woff2 +0 -0
  285. package/dist/client/_assets/chunks/d40ab99c7a38026f411c8f112f742b48-D7Nm7fg6.woff2 +0 -0
  286. package/dist/client/_assets/chunks/d42aafa0f246ad3b4c16fe96d3a3a432-CRxTqZXU.woff2 +0 -0
  287. package/dist/client/_assets/chunks/d4aaf23c13ae808a4bed617afd13aa07-d_T1SKe9.woff2 +0 -0
  288. package/dist/client/_assets/chunks/d509ee5c8241bc7c3de2039d75564fa5-BpNFD9JE.woff2 +0 -0
  289. package/dist/client/_assets/chunks/d675d717cd329bfd4c0524f76ae1579c-Bd5_ctoQ.woff2 +0 -0
  290. package/dist/client/_assets/chunks/d689b1861d7e4377dd72ad3013482612-jPsVOU3F.woff2 +0 -0
  291. package/dist/client/_assets/chunks/d6bb686bddfbe8f38a36a68e609a8667-C_QduS-X.woff2 +0 -0
  292. package/dist/client/_assets/chunks/d9020ff69a83b2a6ee1f42ae480f7db0-DLBUPMCe.woff2 +0 -0
  293. package/dist/client/_assets/chunks/d9047070d72a816b3dba9d40c2d85e69-LTQ42vEp.woff2 +0 -0
  294. package/dist/client/_assets/chunks/da04549f3f4ed28076b01b8cd710d313-CDXOeduw.woff2 +0 -0
  295. package/dist/client/_assets/chunks/da7af303f8c645f9a9dbae0e6e32dd35-DCkS2jqi.woff2 +0 -0
  296. package/dist/client/_assets/chunks/da90a9012ab2d98f759e3fa0820ef502-D0D9d1zg.woff2 +0 -0
  297. package/dist/client/_assets/chunks/dc6e234ded795e91f76d6647f628fbf0-IkooXcRh.woff2 +0 -0
  298. package/dist/client/_assets/chunks/dc8f6256445e68199540be9ade33529f-umb5tWbZ.woff2 +0 -0
  299. package/dist/client/_assets/chunks/dd0cfdfdac0866e66d587e2b5a9e9961-bl4ozE0c.woff2 +0 -0
  300. package/dist/client/_assets/chunks/de304c8a02e45ded4f8dcc479d167198-ClRu89-4.woff2 +0 -0
  301. package/dist/client/_assets/chunks/df10d94bea357a43313e20da5d84bd30-CmAgh2oA.woff2 +0 -0
  302. package/dist/client/_assets/chunks/df9568257eb29b156449fdd4bec5ec76-BMz6aLRr.woff2 +0 -0
  303. package/dist/client/_assets/chunks/e067cd0ed76c90cd0a93c9339253f20b-6RxCQ4jy.woff2 +0 -0
  304. package/dist/client/_assets/chunks/e08b07772e7bed3cec2832d43f7fd339-CQCkeKsr.woff2 +0 -0
  305. package/dist/client/_assets/chunks/e12150d5a39b30be8f567968c7a527b0-DjdoWzeQ.woff2 +0 -0
  306. package/dist/client/_assets/chunks/e34b2b141e472dc776c86fdf8eea23b0-Cjk7H-Ev.woff2 +0 -0
  307. package/dist/client/_assets/chunks/e444fd88f1390636e603d0d681538ac8-Bx7CPOZh.woff2 +0 -0
  308. package/dist/client/_assets/chunks/e53fcb2381eee345db4f6f973dd95a3e-DmkkCUWz.woff2 +0 -0
  309. package/dist/client/_assets/chunks/e5bd313ef81f687d398aacb11cec3069-CwBEbcji.woff2 +0 -0
  310. package/dist/client/_assets/chunks/e79898628283edc27180fc39d0d769c1-By2pplQz.woff2 +0 -0
  311. package/dist/client/_assets/chunks/e8c15be62faf978d208925e79ea6a10d-Ct5M9lTP.woff2 +0 -0
  312. package/dist/client/_assets/chunks/e8c364c16daa04835bf32d293d2598db-3v4Tut_d.woff2 +0 -0
  313. package/dist/client/_assets/chunks/e91c4d941ed9f5c2f3e27f205a3a225e-BwOJ2Lst.woff2 +0 -0
  314. package/dist/client/_assets/chunks/e9c566be7a5d38a9085225f7372bc82b-BCtnCVML.woff2 +0 -0
  315. package/dist/client/_assets/chunks/e9ebc567a711eeb29019ddae3e0ce7fe-CnNhV49O.woff2 +0 -0
  316. package/dist/client/_assets/chunks/eaf332445a40942928e5f5750c1c3116-qZ5LSRrj.woff2 +0 -0
  317. package/dist/client/_assets/chunks/eb5afb3d952b8593782caec6026514b6-BXugZF2T.woff2 +0 -0
  318. package/dist/client/_assets/chunks/ec86e23683052da5cfdc3b77641bd15a-BeLcbcRc.woff2 +0 -0
  319. package/dist/client/_assets/chunks/ecb8875a56c7b038b35432fda41ae128-BVqgNf1o.woff2 +0 -0
  320. package/dist/client/_assets/chunks/eda1b0cb6d1719dd9bedcf3216a9e8de-BJTfLL5z.woff2 +0 -0
  321. package/dist/client/_assets/chunks/edd6a4f608d04fc0351d7688cfc321e4-d3V4U40c.woff2 +0 -0
  322. package/dist/client/_assets/chunks/ee6fce20b420a480714607c66d7f97e5-DEtC-G5G.woff2 +0 -0
  323. package/dist/client/_assets/chunks/eee3836d6ac17ebb2c450bbcbc9db121-C5a2TZjQ.woff2 +0 -0
  324. package/dist/client/_assets/chunks/et-book-bold-line-figures-BFJr2_zv.woff +0 -0
  325. package/dist/client/_assets/chunks/et-book-display-italic-old-style-figures-CoeRJAe8.woff +0 -0
  326. package/dist/client/_assets/chunks/et-book-roman-line-figures-CaA40oOf.woff +0 -0
  327. package/dist/client/_assets/chunks/f1661731474b78bdf81114daf10b254f-KnMKX_U3.woff2 +0 -0
  328. package/dist/client/_assets/chunks/f2c58dee206ba9355046fc23d05491f7-Cm4tCWav.woff2 +0 -0
  329. package/dist/client/_assets/chunks/f50ac27ea4358d67fdda403c2bb52467-WCfYHde1.woff2 +0 -0
  330. package/dist/client/_assets/chunks/f55334112f8e8be82d65db29887a663f-vDq1zH8k.woff2 +0 -0
  331. package/dist/client/_assets/chunks/f5738255e92d8dd34a46d1bcdf4c4074-tpghTzL1.woff2 +0 -0
  332. package/dist/client/_assets/chunks/f740e93f3c0277ecc616594103bca683-C4BgW_ko.woff2 +0 -0
  333. package/dist/client/_assets/chunks/f7633b5af033d76ff2fb3c3c266d77c5-BCkrZxwO.woff2 +0 -0
  334. package/dist/client/_assets/chunks/f7d8468cba2335a83ee414ea68291bab-CVnotkZU.woff2 +0 -0
  335. package/dist/client/_assets/chunks/f91cd722855f4269256eae1187df64ec-DJlAx1A_.woff2 +0 -0
  336. package/dist/client/_assets/chunks/f9695c6c4df2bf6bc03045ff79d4f01f-DDaYGYHT.woff2 +0 -0
  337. package/dist/client/_assets/chunks/fa6e58ce4b52695e7ae19bbea6336ec8-B30jEpze.woff2 +0 -0
  338. package/dist/client/_assets/chunks/fada6eaa68ff8816afe43d2a36c5423e-DlGAWNPb.woff2 +0 -0
  339. package/dist/client/_assets/chunks/fbcc4bf5367218951172bdee6f77d7a6-Dax2VSCd.woff2 +0 -0
  340. package/dist/client/_assets/chunks/fc895f5ce66b656f4a933097bf2a8775-DjjysNdf.woff2 +0 -0
  341. package/dist/client/_assets/chunks/fc9ae5b600fb711f2d67e93ce768cba4-D1lx7LBz.woff2 +0 -0
  342. package/dist/client/_assets/chunks/fca6720fd14c467d29a90f18ef3859b9-B3btrwq3.woff2 +0 -0
  343. package/dist/client/_assets/chunks/fd1bb507bcbf04856eeb7f7fd47ea579-NR1nGJTt.woff2 +0 -0
  344. package/dist/client/_assets/chunks/fd1bceb55d3e0183ac2454b8532fec7d-D3t-v6nn.woff2 +0 -0
  345. package/dist/client/_assets/chunks/fd2069e6e8588c70b4f11364093b81f2-BoXhYVgw.woff2 +0 -0
  346. package/dist/client/_assets/chunks/{url-CG0eolsk.js → url-pLre2DM_.js} +1 -1
  347. package/dist/client/_assets/client-C_kImWZj.css +2 -0
  348. package/dist/client/_assets/client-D95FNDg5.js +272 -0
  349. package/dist/client/_assets/client-auth-CXILhW1b.js +4547 -0
  350. package/dist/client/_assets/client-cjk-jp-DZwrTzQC.css +1 -0
  351. package/dist/client/_assets/client-cjk-kr-_3ZNI2ZP.css +1 -0
  352. package/dist/env-CgaH9Mut.js +252 -0
  353. package/dist/github-api-BkRWnqMx.js +176 -0
  354. package/dist/github-app-WeadXMb8.js +275 -0
  355. package/dist/github-sync-7y_nTXx1.js +4723 -0
  356. package/dist/index.js +88 -2
  357. package/dist/node.js +71 -30
  358. package/dist/url-umUptr5z.js +334 -0
  359. package/package.json +16 -12
  360. package/src/__tests__/bin/content-cli.test.ts +179 -0
  361. package/src/__tests__/bin/media-cli.test.ts +192 -0
  362. package/src/__tests__/export-hugo-build.test.ts +178 -0
  363. package/src/__tests__/export-import-roundtrip.test.ts +265 -0
  364. package/src/__tests__/export-service.test.ts +763 -510
  365. package/src/__tests__/helpers/app.ts +15 -4
  366. package/src/__tests__/helpers/export-fixtures.ts +108 -0
  367. package/src/__tests__/import-site-command.test.ts +358 -350
  368. package/src/__tests__/site-pull-media.test.ts +256 -0
  369. package/src/app.tsx +68 -13
  370. package/src/client/__tests__/collection-form-bridge.test.ts +87 -7
  371. package/src/client/__tests__/collection-page-actions.test.ts +4 -4
  372. package/src/client/__tests__/collection-picker-order.test.ts +53 -0
  373. package/src/client/__tests__/collection-sort-menu.test.ts +2 -2
  374. package/src/client/__tests__/compose-bridge.test.ts +305 -13
  375. package/src/client/__tests__/compose-launch.test.ts +120 -0
  376. package/src/client/__tests__/compose-shortcuts.test.ts +141 -0
  377. package/src/client/__tests__/feed-video-player.test.ts +44 -0
  378. package/src/client/__tests__/site-header-nav.test.ts +250 -0
  379. package/src/client/__tests__/sortable-list.test.ts +6 -2
  380. package/src/client/__tests__/thread-context.test.ts +117 -0
  381. package/src/client/collection-form-bridge.ts +109 -4
  382. package/src/client/collection-page-actions.ts +3 -1
  383. package/src/client/collection-picker-order.ts +104 -0
  384. package/src/client/components/__tests__/{jant-collection-sidebar.test.ts → jant-collection-directory.test.ts} +91 -10
  385. package/src/client/components/__tests__/jant-collection-form.test.ts +40 -18
  386. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1285 -66
  387. package/src/client/components/__tests__/jant-compose-editor.test.ts +254 -19
  388. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +127 -1
  389. package/src/client/components/__tests__/jant-media-lightbox.test.ts +237 -0
  390. package/src/client/components/__tests__/jant-nav-manager.test.ts +326 -0
  391. package/src/client/components/__tests__/jant-post-menu.test.ts +372 -2
  392. package/src/client/components/__tests__/jant-settings-avatar.test.ts +61 -5
  393. package/src/client/components/__tests__/jant-settings-general.test.ts +29 -28
  394. package/src/client/components/__tests__/jant-text-preview.test.ts +206 -0
  395. package/src/client/components/collection-manager-types.ts +18 -1
  396. package/src/client/components/compose-types.ts +48 -0
  397. package/src/client/components/jant-collection-directory.ts +1448 -0
  398. package/src/client/components/jant-collection-form.ts +65 -17
  399. package/src/client/components/jant-command-palette.ts +544 -0
  400. package/src/client/components/jant-compose-dialog.ts +2490 -375
  401. package/src/client/components/jant-compose-editor.ts +420 -75
  402. package/src/client/components/jant-compose-fullscreen.ts +59 -8
  403. package/src/client/components/jant-media-lightbox.ts +373 -18
  404. package/src/client/components/jant-nav-manager.ts +624 -286
  405. package/src/client/components/jant-post-menu.ts +451 -126
  406. package/src/client/components/jant-repo-picker-types.ts +39 -0
  407. package/src/client/components/jant-repo-picker.ts +799 -0
  408. package/src/client/components/jant-settings-avatar.ts +16 -2
  409. package/src/client/components/jant-settings-general.ts +137 -35
  410. package/src/client/components/jant-text-preview.ts +239 -55
  411. package/src/client/components/nav-manager-types.ts +25 -9
  412. package/src/client/components/settings-types.ts +13 -2
  413. package/src/client/compose-bridge.ts +428 -68
  414. package/src/client/compose-launch.ts +41 -4
  415. package/src/client/compose-shortcuts.ts +77 -5
  416. package/src/client/feed-video-player.ts +374 -0
  417. package/src/client/image-processor.ts +53 -127
  418. package/src/client/media-lightbox-events.ts +1 -0
  419. package/src/client/media-scroll-hint.ts +66 -0
  420. package/src/client/multipart-upload.ts +4 -1
  421. package/src/client/palette-shortcuts.ts +35 -0
  422. package/src/client/search-rank.ts +74 -0
  423. package/src/client/settings-bridge.ts +7 -0
  424. package/src/client/site-header-nav.d.ts +1 -0
  425. package/src/client/site-header-nav.js +139 -51
  426. package/src/client/sortable-list.ts +2 -0
  427. package/src/client/thread-context.ts +54 -11
  428. package/src/client/tiptap/__tests__/block-insertion.test.ts +65 -0
  429. package/src/client/tiptap/__tests__/footnotes.test.ts +692 -0
  430. package/src/client/tiptap/__tests__/insert-paragraph-around.test.ts +228 -0
  431. package/src/client/tiptap/__tests__/link-toolbar.test.ts +120 -4
  432. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +48 -0
  433. package/src/client/tiptap/block-insertion.ts +36 -0
  434. package/src/client/tiptap/bubble-menu.ts +34 -4
  435. package/src/client/tiptap/create-editor.ts +49 -30
  436. package/src/client/tiptap/embed-dialog.ts +301 -0
  437. package/src/client/tiptap/embed-node.ts +373 -0
  438. package/src/client/tiptap/embed-paste.ts +91 -0
  439. package/src/client/tiptap/extensions.ts +80 -28
  440. package/src/client/tiptap/footnotes.ts +718 -0
  441. package/src/client/tiptap/html-block-node.ts +222 -0
  442. package/src/client/tiptap/image-node.ts +49 -6
  443. package/src/client/tiptap/inline-image-upload.ts +200 -40
  444. package/src/client/tiptap/insert-paragraph-around.ts +79 -0
  445. package/src/client/tiptap/link-input-rules.ts +50 -16
  446. package/src/client/tiptap/link-toolbar.ts +156 -133
  447. package/src/client/tiptap/markdown-clipboard.ts +120 -0
  448. package/src/client/tiptap/more-break.ts +36 -3
  449. package/src/client/tiptap/paste-media.ts +6 -0
  450. package/src/client/tiptap/slash-commands.ts +167 -14
  451. package/src/client/tiptap/tab-indent.ts +184 -0
  452. package/src/client/tiptap/toolbar-mode.ts +15 -2
  453. package/src/client/tiptap/wrapping-input-rules.ts +102 -0
  454. package/src/client/types/sortablejs.d.ts +9 -1
  455. package/src/client/upload-session.ts +2 -1
  456. package/src/client/video-processor.ts +177 -20
  457. package/src/client-auth.ts +9 -1
  458. package/src/client-site.ts +15 -0
  459. package/src/client.ts +2 -0
  460. package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
  461. package/src/db/__tests__/migration-rehearsal.test.ts +5 -1
  462. package/src/db/__tests__/migrations.test.ts +25 -2
  463. package/src/db/backfills/0001_strip_collection_path_c_prefix.sql +11 -0
  464. package/src/db/backfills/0002_strip_collection_path_c_prefix_fix.sql +12 -0
  465. package/src/db/backfills/0003_clear_system_nav_default_labels.sql +15 -0
  466. package/src/db/dialect.ts +40 -0
  467. package/src/db/migrations/0005_busy_the_phantom.sql +39 -0
  468. package/src/db/migrations/0006_adorable_magdalene.sql +3 -0
  469. package/src/db/migrations/0007_unusual_warstar.sql +1 -0
  470. package/src/db/migrations/0008_nasty_lockheed.sql +1 -0
  471. package/src/db/migrations/0009_clear_fixer.sql +31 -0
  472. package/src/db/migrations/0010_futuristic_preak.sql +31 -0
  473. package/src/db/migrations/0011_bizarre_smasher.sql +30 -0
  474. package/src/db/migrations/0012_furry_thena.sql +52 -0
  475. package/src/db/migrations/0013_mixed_lightspeed.sql +39 -0
  476. package/src/db/migrations/0014_high_the_santerians.sql +41 -0
  477. package/src/db/migrations/0015_skinny_shinobi_shaw.sql +1 -0
  478. package/src/db/migrations/0016_remarkable_nicolaos.sql +17 -0
  479. package/src/db/migrations/0017_powerful_moonstone.sql +14 -0
  480. package/src/db/migrations/0018_yummy_franklin_richards.sql +6 -0
  481. package/src/db/migrations/meta/0005_snapshot.json +1904 -0
  482. package/src/db/migrations/meta/0006_snapshot.json +1925 -0
  483. package/src/db/migrations/meta/0007_snapshot.json +1933 -0
  484. package/src/db/migrations/meta/0008_snapshot.json +1940 -0
  485. package/src/db/migrations/meta/0009_snapshot.json +1952 -0
  486. package/src/db/migrations/meta/0010_snapshot.json +1952 -0
  487. package/src/db/migrations/meta/0011_snapshot.json +1948 -0
  488. package/src/db/migrations/meta/0012_snapshot.json +1955 -0
  489. package/src/db/migrations/meta/0013_snapshot.json +1977 -0
  490. package/src/db/migrations/meta/0014_snapshot.json +1988 -0
  491. package/src/db/migrations/meta/0015_snapshot.json +1995 -0
  492. package/src/db/migrations/meta/0016_snapshot.json +2104 -0
  493. package/src/db/migrations/meta/0017_snapshot.json +2188 -0
  494. package/src/db/migrations/meta/0018_snapshot.json +2225 -0
  495. package/src/db/migrations/meta/_journal.json +98 -0
  496. package/src/db/migrations/pg/0003_motionless_norrin_radd.sql +21 -0
  497. package/src/db/migrations/pg/0004_nervous_captain_midlands.sql +3 -0
  498. package/src/db/migrations/pg/0005_romantic_mesmero.sql +1 -0
  499. package/src/db/migrations/pg/0006_perpetual_bruce_banner.sql +1 -0
  500. package/src/db/migrations/pg/0007_nav_item_placement.sql +1 -0
  501. package/src/db/migrations/pg/0008_yielding_frightful_four.sql +3 -0
  502. package/src/db/migrations/pg/0009_outstanding_ogun.sql +1 -0
  503. package/src/db/migrations/pg/0010_overjoyed_gertrude_yorkes.sql +28 -0
  504. package/src/db/migrations/pg/0011_fixed_hulk.sql +19 -0
  505. package/src/db/migrations/pg/0012_cute_shockwave.sql +2 -0
  506. package/src/db/migrations/pg/0013_bizarre_obadiah_stane.sql +1 -0
  507. package/src/db/migrations/pg/0014_tearful_grim_reaper.sql +16 -0
  508. package/src/db/migrations/pg/0015_daffy_mikhail_rasputin.sql +14 -0
  509. package/src/db/migrations/pg/0016_familiar_lionheart.sql +6 -0
  510. package/src/db/migrations/pg/meta/0003_snapshot.json +2482 -0
  511. package/src/db/migrations/pg/meta/0004_snapshot.json +2500 -0
  512. package/src/db/migrations/pg/meta/0005_snapshot.json +2507 -0
  513. package/src/db/migrations/pg/meta/0006_snapshot.json +2513 -0
  514. package/src/db/migrations/pg/meta/0008_snapshot.json +2524 -0
  515. package/src/db/migrations/pg/meta/0009_snapshot.json +2520 -0
  516. package/src/db/migrations/pg/meta/0010_snapshot.json +2526 -0
  517. package/src/db/migrations/pg/meta/0011_snapshot.json +2563 -0
  518. package/src/db/migrations/pg/meta/0012_snapshot.json +2573 -0
  519. package/src/db/migrations/pg/meta/0013_snapshot.json +2579 -0
  520. package/src/db/migrations/pg/meta/0014_snapshot.json +2702 -0
  521. package/src/db/migrations/pg/meta/0015_snapshot.json +2803 -0
  522. package/src/db/migrations/pg/meta/0016_snapshot.json +2840 -0
  523. package/src/db/migrations/pg/meta/_journal.json +98 -0
  524. package/src/db/pg/__tests__/node.test.ts +38 -0
  525. package/src/db/pg/node.ts +149 -0
  526. package/src/db/pg/schema.ts +148 -12
  527. package/src/db/rehearsal-fixtures/demo-current.sql +47 -3
  528. package/src/db/schema.ts +162 -12
  529. package/src/i18n/Trans.tsx +3 -3
  530. package/src/i18n/__tests__/context.test.tsx +9 -6
  531. package/src/i18n/__tests__/detect.test.ts +65 -48
  532. package/src/i18n/__tests__/fallback.test.ts +40 -0
  533. package/src/i18n/__tests__/message-placeholders.test.ts +43 -0
  534. package/src/i18n/__tests__/middleware.test.ts +83 -0
  535. package/src/i18n/context.tsx +9 -44
  536. package/src/i18n/detect.ts +95 -28
  537. package/src/i18n/i18n.ts +41 -17
  538. package/src/i18n/index.ts +7 -5
  539. package/src/i18n/locales/{en.po → public/en.po} +284 -1151
  540. package/src/i18n/locales/public/en.ts +1 -0
  541. package/src/i18n/locales/public/zh-Hans.po +2327 -0
  542. package/src/i18n/locales/public/zh-Hans.ts +1 -0
  543. package/src/i18n/locales/public/zh-Hant.po +2327 -0
  544. package/src/i18n/locales/public/zh-Hant.ts +1 -0
  545. package/src/i18n/locales/settings/en.po +1622 -0
  546. package/src/i18n/locales/settings/en.ts +1 -0
  547. package/src/i18n/locales/settings/zh-Hans.po +1622 -0
  548. package/src/i18n/locales/settings/zh-Hans.ts +1 -0
  549. package/src/i18n/locales/settings/zh-Hant.po +1622 -0
  550. package/src/i18n/locales/settings/zh-Hant.ts +1 -0
  551. package/src/i18n/middleware.ts +37 -24
  552. package/src/index.ts +4 -6
  553. package/src/lib/__tests__/collection-groups.test.ts +17 -0
  554. package/src/lib/__tests__/constants.test.ts +2 -24
  555. package/src/lib/__tests__/csp-builder.test.ts +68 -0
  556. package/src/lib/__tests__/display-text.test.ts +27 -0
  557. package/src/lib/__tests__/embed-providers.test.ts +104 -0
  558. package/src/lib/__tests__/embed-render.test.ts +60 -0
  559. package/src/lib/__tests__/feed.test.ts +125 -63
  560. package/src/lib/__tests__/hosted-signin.test.ts +41 -2
  561. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +88 -0
  562. package/src/lib/__tests__/markdown-to-tiptap.test.ts +59 -0
  563. package/src/lib/__tests__/markdown.test.ts +47 -5
  564. package/src/lib/__tests__/navigation.test.ts +132 -0
  565. package/src/lib/__tests__/post-display.test.ts +3 -2
  566. package/src/lib/__tests__/post-meta.test.ts +3 -0
  567. package/src/lib/__tests__/rate-limit-d1.test.ts +82 -0
  568. package/src/lib/__tests__/rate-limit-memory.test.ts +69 -0
  569. package/src/lib/__tests__/resolve-config.test.ts +30 -25
  570. package/src/lib/__tests__/schemas.test.ts +20 -3
  571. package/src/lib/__tests__/summary.test.ts +540 -1
  572. package/src/lib/__tests__/theme.test.ts +41 -2
  573. package/src/lib/__tests__/timeline.test.ts +33 -27
  574. package/src/lib/__tests__/tiptap-render.test.ts +224 -0
  575. package/src/lib/__tests__/tiptap-to-markdown.test.ts +51 -0
  576. package/src/lib/__tests__/video-playback.test.ts +48 -0
  577. package/src/lib/__tests__/view.test.ts +181 -8
  578. package/src/lib/__tests__/worker-response-cache.test.ts +311 -0
  579. package/src/lib/__tests__/youtube.test.ts +99 -0
  580. package/src/lib/api-media.ts +55 -0
  581. package/src/lib/api-posts.ts +99 -0
  582. package/src/lib/api-search.ts +47 -0
  583. package/src/lib/api-settings.ts +122 -0
  584. package/src/lib/asset-path.ts +20 -2
  585. package/src/lib/collection-groups.ts +2 -1
  586. package/src/lib/collection-paths.ts +40 -0
  587. package/src/lib/constants.ts +3 -24
  588. package/src/lib/csp-builder.ts +99 -0
  589. package/src/lib/decorative-quote-mark.ts +11 -0
  590. package/src/lib/display-text.ts +56 -0
  591. package/src/lib/embed-providers.ts +289 -0
  592. package/src/lib/embed-render.ts +151 -0
  593. package/src/lib/env.ts +70 -1
  594. package/src/lib/excerpt.ts +11 -2
  595. package/src/lib/feed.ts +154 -95
  596. package/src/lib/footnotes.ts +146 -0
  597. package/src/lib/github-api.ts +423 -0
  598. package/src/lib/github-app-state.ts +135 -0
  599. package/src/lib/github-app.ts +487 -0
  600. package/src/lib/github-sync-queue-handler.ts +69 -0
  601. package/src/lib/github-sync-site-config.ts +57 -0
  602. package/src/lib/github-sync-status.tsx +87 -0
  603. package/src/lib/github-sync-trigger.ts +199 -0
  604. package/src/lib/github-sync-worker.ts +72 -0
  605. package/src/lib/hosted-signin.ts +9 -3
  606. package/src/lib/hugo-markdown.ts +255 -0
  607. package/src/lib/job-queue-cf.ts +18 -0
  608. package/src/lib/job-queue-db.ts +149 -0
  609. package/src/lib/job-queue.ts +35 -0
  610. package/src/lib/markdown-manager.ts +864 -0
  611. package/src/lib/markdown-to-tiptap.ts +4 -323
  612. package/src/lib/markdown.ts +16 -24
  613. package/src/lib/media-helpers.ts +1 -0
  614. package/src/lib/navigation.ts +50 -19
  615. package/src/lib/post-display.ts +10 -22
  616. package/src/lib/post-meta.ts +20 -2
  617. package/src/lib/public-storage.ts +6 -1
  618. package/src/lib/rate-limit-d1.ts +99 -0
  619. package/src/lib/rate-limit-memory.ts +105 -0
  620. package/src/lib/rate-limit.ts +63 -0
  621. package/src/lib/render.tsx +14 -2
  622. package/src/lib/resolve-config.ts +32 -11
  623. package/src/lib/rich-image.ts +89 -0
  624. package/src/lib/schemas.ts +180 -13
  625. package/src/lib/summary.ts +162 -20
  626. package/src/lib/theme.ts +119 -0
  627. package/src/lib/timeline.ts +63 -42
  628. package/src/lib/tiptap-render.ts +265 -126
  629. package/src/lib/tiptap-to-markdown.ts +6 -329
  630. package/src/lib/upload.ts +97 -6
  631. package/src/lib/url.ts +34 -0
  632. package/src/lib/version.ts +18 -0
  633. package/src/lib/video-playback.ts +73 -0
  634. package/src/lib/view.ts +168 -94
  635. package/src/lib/webhook-signature.ts +65 -0
  636. package/src/lib/worker-response-cache.ts +220 -0
  637. package/src/lib/youtube.ts +119 -0
  638. package/src/middleware/__tests__/auth.test.ts +44 -4
  639. package/src/middleware/__tests__/onboarding.test.ts +3 -3
  640. package/src/middleware/__tests__/rate-limit.test.ts +113 -0
  641. package/src/middleware/__tests__/secure-headers.test.ts +28 -1
  642. package/src/middleware/__tests__/session.test.ts +85 -0
  643. package/src/middleware/auth.ts +62 -25
  644. package/src/middleware/config.ts +7 -10
  645. package/src/middleware/cors.ts +49 -0
  646. package/src/middleware/rate-limit.ts +54 -0
  647. package/src/middleware/secure-headers.ts +45 -55
  648. package/src/middleware/session.ts +36 -0
  649. package/src/node/__tests__/cli-migrate.test.ts +111 -0
  650. package/src/node/__tests__/cli-runtime-target.test.ts +1 -1
  651. package/src/node/__tests__/cli-site-token-env.test.ts +2 -2
  652. package/src/preset.css +316 -12
  653. package/src/routes/__tests__/compose.test.ts +4 -2
  654. package/src/routes/api/__tests__/collections.test.ts +81 -27
  655. package/src/routes/api/__tests__/mcp.test.ts +389 -0
  656. package/src/routes/api/__tests__/nav-items.test.ts +163 -2
  657. package/src/routes/api/__tests__/posts.test.ts +190 -0
  658. package/src/routes/api/__tests__/search.test.ts +48 -0
  659. package/src/routes/api/__tests__/settings.test.ts +99 -3
  660. package/src/routes/api/__tests__/upload-multipart.test.ts +21 -6
  661. package/src/routes/api/__tests__/upload.test.ts +132 -0
  662. package/src/routes/api/__tests__/uploads.test.ts +5 -3
  663. package/src/routes/api/collections.ts +67 -26
  664. package/src/routes/api/custom-urls.ts +1 -0
  665. package/src/routes/api/export.ts +8 -4
  666. package/src/routes/api/github-sync.tsx +380 -0
  667. package/src/routes/api/internal/__tests__/sites.test.ts +45 -2
  668. package/src/routes/api/internal/search-reindex.ts +40 -0
  669. package/src/routes/api/internal/sites.ts +2 -0
  670. package/src/routes/api/internal/text-attachments.ts +60 -0
  671. package/src/routes/api/internal/uploads.ts +5 -1
  672. package/src/routes/api/mcp.ts +30 -0
  673. package/src/routes/api/nav-items.ts +30 -11
  674. package/src/routes/api/palette.ts +17 -0
  675. package/src/routes/api/posts.ts +37 -106
  676. package/src/routes/api/public/__tests__/posts.test.ts +372 -0
  677. package/src/routes/api/public/posts.ts +302 -0
  678. package/src/routes/api/search.ts +13 -46
  679. package/src/routes/api/settings.ts +44 -58
  680. package/src/routes/api/upload-multipart.ts +15 -5
  681. package/src/routes/api/upload.ts +62 -49
  682. package/src/routes/api/uploads.ts +1 -0
  683. package/src/routes/auth/__tests__/hosted-sso.test.ts +33 -0
  684. package/src/routes/auth/__tests__/setup.test.ts +29 -4
  685. package/src/routes/auth/dev.ts +1 -1
  686. package/src/routes/auth/hosted-sso-expired-page.tsx +89 -0
  687. package/src/routes/auth/hosted-sso.ts +42 -1
  688. package/src/routes/auth/reset.tsx +46 -32
  689. package/src/routes/auth/setup.tsx +53 -43
  690. package/src/routes/auth/signin.tsx +57 -29
  691. package/src/routes/compose.tsx +114 -1
  692. package/src/routes/dash/__tests__/font-theme.test.ts +6 -27
  693. package/src/routes/dash/__tests__/settings-avatar.test.ts +2 -3
  694. package/src/routes/dash/custom-urls.tsx +250 -112
  695. package/src/routes/dash/settings.tsx +1182 -75
  696. package/src/routes/feed/__tests__/{rss.test.ts → feed.test.ts} +62 -123
  697. package/src/routes/feed/__tests__/sitemap.test.ts +320 -4
  698. package/src/routes/feed/feed.ts +386 -0
  699. package/src/routes/feed/manifest.ts +67 -0
  700. package/src/routes/feed/sitemap.ts +211 -27
  701. package/src/routes/pages/__tests__/collection-routing.test.ts +176 -0
  702. package/src/routes/pages/__tests__/collections.test.ts +2 -3
  703. package/src/routes/pages/__tests__/featured.test.ts +2 -3
  704. package/src/routes/pages/__tests__/page-canonical.test.ts +101 -0
  705. package/src/routes/pages/archive.tsx +523 -127
  706. package/src/routes/pages/collection.tsx +197 -43
  707. package/src/routes/pages/collections.tsx +5 -1
  708. package/src/routes/pages/home.tsx +26 -17
  709. package/src/routes/pages/latest.tsx +3 -3
  710. package/src/routes/pages/page.tsx +255 -3
  711. package/src/routes/pages/partials.tsx +8 -14
  712. package/src/routes/pages/search.tsx +17 -1
  713. package/src/routes/pages/theme-sample.tsx +1 -2
  714. package/src/runtime/__tests__/node.test.ts +1 -1
  715. package/src/runtime/cloudflare.ts +4 -0
  716. package/src/runtime/node.ts +16 -0
  717. package/src/runtime/readiness.ts +1 -1
  718. package/src/runtime/site.ts +9 -1
  719. package/src/services/__tests__/collection.test.ts +137 -54
  720. package/src/services/__tests__/github-app-installations.test.ts +181 -0
  721. package/src/services/__tests__/github-sync-classify.test.ts +189 -0
  722. package/src/services/__tests__/github-sync-push.test.ts +159 -0
  723. package/src/services/__tests__/media.test.ts +444 -7
  724. package/src/services/__tests__/navigation.test.ts +271 -14
  725. package/src/services/__tests__/post-timeline.test.ts +31 -21
  726. package/src/services/__tests__/post.test.ts +288 -9
  727. package/src/services/__tests__/search.test.ts +44 -0
  728. package/src/services/__tests__/settings.test.ts +43 -21
  729. package/src/services/auth.ts +2 -2
  730. package/src/services/bootstrap.ts +10 -7
  731. package/src/services/collection.ts +591 -193
  732. package/src/services/custom-url.ts +34 -7
  733. package/src/services/export-theme/assets/client-site.css +2 -0
  734. package/src/services/export-theme/assets/client-site.js +145 -0
  735. package/src/services/export-theme/layouts/_default/alias.html +49 -0
  736. package/src/services/export-theme/layouts/_default/baseof.html +21 -0
  737. package/src/services/export-theme/layouts/_default/list.html +104 -0
  738. package/src/services/export-theme/layouts/_default/rss.xml +160 -0
  739. package/src/services/export-theme/layouts/_default/single.html +15 -0
  740. package/src/services/export-theme/layouts/archive/list.html +31 -0
  741. package/src/services/export-theme/layouts/collection/single.html +19 -0
  742. package/src/services/export-theme/layouts/collections/list.html +111 -0
  743. package/src/services/export-theme/layouts/featured/list.html +28 -0
  744. package/src/services/export-theme/layouts/index.html +41 -0
  745. package/src/services/export-theme/layouts/partials/feed-post-content.xml +107 -0
  746. package/src/services/export-theme/layouts/partials/footer.html +32 -0
  747. package/src/services/export-theme/layouts/partials/head.html +52 -0
  748. package/src/services/export-theme/layouts/partials/header.html +115 -0
  749. package/src/services/export-theme/layouts/partials/media-gallery.html +246 -0
  750. package/src/services/export-theme/layouts/partials/pagination.html +55 -0
  751. package/src/services/export-theme/layouts/partials/post-card.html +127 -0
  752. package/src/services/export-theme/layouts/partials/reply.html +91 -0
  753. package/src/services/export-theme/layouts/partials/thread-preview.html +82 -0
  754. package/src/services/export-theme/layouts/post/list.html +129 -0
  755. package/src/services/export-theme/styles/main.css +1981 -0
  756. package/src/services/export-theme/theme.toml +12 -0
  757. package/src/services/export.ts +1372 -2412
  758. package/src/services/github-app-installations.ts +302 -0
  759. package/src/services/github-sync.ts +769 -0
  760. package/src/services/index.ts +15 -0
  761. package/src/services/mcp.ts +1324 -0
  762. package/src/services/media.ts +336 -25
  763. package/src/services/navigation.ts +115 -23
  764. package/src/services/path.ts +129 -28
  765. package/src/services/post.ts +858 -197
  766. package/src/services/search.ts +3 -0
  767. package/src/services/settings.ts +29 -15
  768. package/src/services/site-admin.ts +26 -6
  769. package/src/services/upload-session.ts +69 -34
  770. package/src/style-cjk-jp.css +3 -0
  771. package/src/style-cjk-kr.css +3 -0
  772. package/src/styles/components.css +160 -59
  773. package/src/styles/fonts/et-book/et-book-bold-line-figures.woff +0 -0
  774. package/src/styles/fonts/et-book/et-book-display-italic-old-style-figures.woff +0 -0
  775. package/src/styles/fonts/et-book/et-book-roman-line-figures.woff +0 -0
  776. package/src/styles/fonts/et-book.css +31 -0
  777. package/src/styles/fonts/latin.css +1 -0
  778. package/src/styles/fonts/noto-serif-jp/400/057a6a98bda7fe57105ddaa99ec82015.woff2 +0 -0
  779. package/src/styles/fonts/noto-serif-jp/400/0acd1fe2b2ea1ad1bfee7ae1fa139d27.woff2 +0 -0
  780. package/src/styles/fonts/noto-serif-jp/400/0c2ab6b295e55f356f8020d4e7747522.woff2 +0 -0
  781. package/src/styles/fonts/noto-serif-jp/400/0c5f9492af03a4fa42c784de94649de1.woff2 +0 -0
  782. package/src/styles/fonts/noto-serif-jp/400/0d9a936885a4c39077438effd3779cbd.woff2 +0 -0
  783. package/src/styles/fonts/noto-serif-jp/400/0e645da524f7cfc0e8c3c03fb2b08428.woff2 +0 -0
  784. package/src/styles/fonts/noto-serif-jp/400/135e83b403475c5dc9e49b43501a5b84.woff2 +0 -0
  785. package/src/styles/fonts/noto-serif-jp/400/13b2c53b30e6a3e4de7132dbc18dbdfc.woff2 +0 -0
  786. package/src/styles/fonts/noto-serif-jp/400/15d95680dc31cc6ce20e0a5464106dc4.woff2 +0 -0
  787. package/src/styles/fonts/noto-serif-jp/400/1733f27476507ca68b68a803bc533cc2.woff2 +0 -0
  788. package/src/styles/fonts/noto-serif-jp/400/188c2db794f3dd7a45889ddbc81da9bb.woff2 +0 -0
  789. package/src/styles/fonts/noto-serif-jp/400/1cf737900dd49c2e88f1b3221a82a602.woff2 +0 -0
  790. package/src/styles/fonts/noto-serif-jp/400/233d3a685ee18276b319363474599d47.woff2 +0 -0
  791. package/src/styles/fonts/noto-serif-jp/400/284b53bbefb06924cf236d24c8ed5641.woff2 +0 -0
  792. package/src/styles/fonts/noto-serif-jp/400/2a56eaf19d1d38a6b57e2a388f733676.woff2 +0 -0
  793. package/src/styles/fonts/noto-serif-jp/400/2abfbab82b6a7c04426afc054d2464da.woff2 +0 -0
  794. package/src/styles/fonts/noto-serif-jp/400/2e6f4bb71ef6b38765d51acc5a79f638.woff2 +0 -0
  795. package/src/styles/fonts/noto-serif-jp/400/31194d303a67561926a544ed0e072aee.woff2 +0 -0
  796. package/src/styles/fonts/noto-serif-jp/400/3623d96698132c61e861f984ce11cc23.woff2 +0 -0
  797. package/src/styles/fonts/noto-serif-jp/400/39211b02f24c69cbd1b54f6d41b01933.woff2 +0 -0
  798. package/src/styles/fonts/noto-serif-jp/400/3e24063b19abd2b051af986a6870046d.woff2 +0 -0
  799. package/src/styles/fonts/noto-serif-jp/400/4410f876d27c9821839474b6b1059fd7.woff2 +0 -0
  800. package/src/styles/fonts/noto-serif-jp/400/479329a39a3eb6c0047e6b0981919855.woff2 +0 -0
  801. package/src/styles/fonts/noto-serif-jp/400/527ff336279465617aafbafe1a830632.woff2 +0 -0
  802. package/src/styles/fonts/noto-serif-jp/400/56b40518ea0608e62826bd44ee27f5f9.woff2 +0 -0
  803. package/src/styles/fonts/noto-serif-jp/400/5acc7f736217259db79a42bf44241c48.woff2 +0 -0
  804. package/src/styles/fonts/noto-serif-jp/400/5c05f8b75c9d7f25b3c04ca711cf43b3.woff2 +0 -0
  805. package/src/styles/fonts/noto-serif-jp/400/5e0124c7265f1b4cf0fc797b94efbebc.woff2 +0 -0
  806. package/src/styles/fonts/noto-serif-jp/400/615a7e0d63f257109d599b2a1977de68.woff2 +0 -0
  807. package/src/styles/fonts/noto-serif-jp/400/61a2c80d0c924d5a6187c02e8d1d1642.woff2 +0 -0
  808. package/src/styles/fonts/noto-serif-jp/400/62fc94f8d4c5a750b7f25e7387539910.woff2 +0 -0
  809. package/src/styles/fonts/noto-serif-jp/400/639c7c6b0b0c27c738702741cfa4b8c0.woff2 +0 -0
  810. package/src/styles/fonts/noto-serif-jp/400/68a3a4bf337f8a0722be76676e20b850.woff2 +0 -0
  811. package/src/styles/fonts/noto-serif-jp/400/6abdd163d2c4b85698db8aa1ce149361.woff2 +0 -0
  812. package/src/styles/fonts/noto-serif-jp/400/73b794d885b88f7befb7ea8ab1780a15.woff2 +0 -0
  813. package/src/styles/fonts/noto-serif-jp/400/743f290baf027d4626a86e22f3d44600.woff2 +0 -0
  814. package/src/styles/fonts/noto-serif-jp/400/757019c01c5e2c2b0f54293cea3b5636.woff2 +0 -0
  815. package/src/styles/fonts/noto-serif-jp/400/76f117a3baacda304a71965a17266a13.woff2 +0 -0
  816. package/src/styles/fonts/noto-serif-jp/400/77747f17625a259fb405b7bbdd84ac4b.woff2 +0 -0
  817. package/src/styles/fonts/noto-serif-jp/400/788498548ddfb710d6ef3c7bff79fa97.woff2 +0 -0
  818. package/src/styles/fonts/noto-serif-jp/400/78ea6c40923ce95367e3517dd1e5a849.woff2 +0 -0
  819. package/src/styles/fonts/noto-serif-jp/400/7f9a6c9286b68de9c72b0024f7beeb40.woff2 +0 -0
  820. package/src/styles/fonts/noto-serif-jp/400/823d1bb11097331238d103c7f72138dc.woff2 +0 -0
  821. package/src/styles/fonts/noto-serif-jp/400/888ba53510213d5d1f6a7deb60e569b6.woff2 +0 -0
  822. package/src/styles/fonts/noto-serif-jp/400/892aa49b2529c89bb4076d4aa51fe30f.woff2 +0 -0
  823. package/src/styles/fonts/noto-serif-jp/400/95ff41191c76ef893ac70a1043e95e44.woff2 +0 -0
  824. package/src/styles/fonts/noto-serif-jp/400/96c75862c8cec51a7c05ff025fea86cd.woff2 +0 -0
  825. package/src/styles/fonts/noto-serif-jp/400/9b21e8244f5930c48ad5073f83777b6c.woff2 +0 -0
  826. package/src/styles/fonts/noto-serif-jp/400/9fce02d1a09f464c36c9fcfb14a354c5.woff2 +0 -0
  827. package/src/styles/fonts/noto-serif-jp/400/a0a6755c7e3f9e4cf03387027dd9f16c.woff2 +0 -0
  828. package/src/styles/fonts/noto-serif-jp/400/a4475c442c6b259354c9eda62e64e7df.woff2 +0 -0
  829. package/src/styles/fonts/noto-serif-jp/400/a4a39b5f048671aa37c2b2a9581ab08a.woff2 +0 -0
  830. package/src/styles/fonts/noto-serif-jp/400/a534449d67561ac9b06489f64e57ad98.woff2 +0 -0
  831. package/src/styles/fonts/noto-serif-jp/400/a919a8c6fec6d7e8bb21177965f2e9d7.woff2 +0 -0
  832. package/src/styles/fonts/noto-serif-jp/400/ad67113f88b59582991cb0c5d33ea19f.woff2 +0 -0
  833. package/src/styles/fonts/noto-serif-jp/400/adb6e613fb99c6dd660b727101f554a8.woff2 +0 -0
  834. package/src/styles/fonts/noto-serif-jp/400/adc4a98a89870ef984ee8f4b57c8a3a5.woff2 +0 -0
  835. package/src/styles/fonts/noto-serif-jp/400/b12379ab782468d725519cd07a7d15bc.woff2 +0 -0
  836. package/src/styles/fonts/noto-serif-jp/400/b55486f8a459c838fe329d4e79a8c211.woff2 +0 -0
  837. package/src/styles/fonts/noto-serif-jp/400/bb0b15492b8cdbbec57c0bcfa0aa9241.woff2 +0 -0
  838. package/src/styles/fonts/noto-serif-jp/400/bedc74b423b7293b6ad0bdecc61c42cc.woff2 +0 -0
  839. package/src/styles/fonts/noto-serif-jp/400/bfb00d4a4c48661bd0be99f300a0faae.woff2 +0 -0
  840. package/src/styles/fonts/noto-serif-jp/400/c1b0df29ae41d764904df84e9ac83d1e.woff2 +0 -0
  841. package/src/styles/fonts/noto-serif-jp/400/c42c67070cfe99cf823df92d81c7fa6e.woff2 +0 -0
  842. package/src/styles/fonts/noto-serif-jp/400/c4d749e45ecd5a5aed5a0bb3ebfd355d.woff2 +0 -0
  843. package/src/styles/fonts/noto-serif-jp/400/c96d83978add28b356c22c4c84916733.woff2 +0 -0
  844. package/src/styles/fonts/noto-serif-jp/400/cb7ccd6494256f7a2977b7c2b0225592.woff2 +0 -0
  845. package/src/styles/fonts/noto-serif-jp/400/ce41d70ce6a069a498525c9e15c45cf2.woff2 +0 -0
  846. package/src/styles/fonts/noto-serif-jp/400/d509ee5c8241bc7c3de2039d75564fa5.woff2 +0 -0
  847. package/src/styles/fonts/noto-serif-jp/400/d675d717cd329bfd4c0524f76ae1579c.woff2 +0 -0
  848. package/src/styles/fonts/noto-serif-jp/400/dc8f6256445e68199540be9ade33529f.woff2 +0 -0
  849. package/src/styles/fonts/noto-serif-jp/400/e8c15be62faf978d208925e79ea6a10d.woff2 +0 -0
  850. package/src/styles/fonts/noto-serif-jp/400/e91c4d941ed9f5c2f3e27f205a3a225e.woff2 +0 -0
  851. package/src/styles/fonts/noto-serif-jp/400/e9c566be7a5d38a9085225f7372bc82b.woff2 +0 -0
  852. package/src/styles/fonts/noto-serif-jp/400/e9ebc567a711eeb29019ddae3e0ce7fe.woff2 +0 -0
  853. package/src/styles/fonts/noto-serif-jp/400/ecb8875a56c7b038b35432fda41ae128.woff2 +0 -0
  854. package/src/styles/fonts/noto-serif-jp/400/f1661731474b78bdf81114daf10b254f.woff2 +0 -0
  855. package/src/styles/fonts/noto-serif-jp/400/f2c58dee206ba9355046fc23d05491f7.woff2 +0 -0
  856. package/src/styles/fonts/noto-serif-jp/400/f50ac27ea4358d67fdda403c2bb52467.woff2 +0 -0
  857. package/src/styles/fonts/noto-serif-jp/400/f55334112f8e8be82d65db29887a663f.woff2 +0 -0
  858. package/src/styles/fonts/noto-serif-jp/400/f740e93f3c0277ecc616594103bca683.woff2 +0 -0
  859. package/src/styles/fonts/noto-serif-jp/400/f7633b5af033d76ff2fb3c3c266d77c5.woff2 +0 -0
  860. package/src/styles/fonts/noto-serif-jp/400/f7d8468cba2335a83ee414ea68291bab.woff2 +0 -0
  861. package/src/styles/fonts/noto-serif-jp/400/fc9ae5b600fb711f2d67e93ce768cba4.woff2 +0 -0
  862. package/src/styles/fonts/noto-serif-jp/400/fd1bceb55d3e0183ac2454b8532fec7d.woff2 +0 -0
  863. package/src/styles/fonts/noto-serif-jp/700/02611a045a7fe83a12014e3debc9f731.woff2 +0 -0
  864. package/src/styles/fonts/noto-serif-jp/700/0f5e1a18987dbc84ca05188c129e1936.woff2 +0 -0
  865. package/src/styles/fonts/noto-serif-jp/700/112743a4ab5fdd1498dfdf2b11336380.woff2 +0 -0
  866. package/src/styles/fonts/noto-serif-jp/700/1211f03d3ff5759a702631445793f60e.woff2 +0 -0
  867. package/src/styles/fonts/noto-serif-jp/700/147b24c2f08d03bbed30887d4cb3311c.woff2 +0 -0
  868. package/src/styles/fonts/noto-serif-jp/700/16663e567f81f4725a1522f37e18f71f.woff2 +0 -0
  869. package/src/styles/fonts/noto-serif-jp/700/1824321da801e6257b902f3d1d09054c.woff2 +0 -0
  870. package/src/styles/fonts/noto-serif-jp/700/1a3a92c7c060a71a6c35819b9ebcdbb9.woff2 +0 -0
  871. package/src/styles/fonts/noto-serif-jp/700/1fba7ec0e412e911bf31841de5a8a4e4.woff2 +0 -0
  872. package/src/styles/fonts/noto-serif-jp/700/28a97af9ab9d38983d20f39ff09840e1.woff2 +0 -0
  873. package/src/styles/fonts/noto-serif-jp/700/2bfaadaf3479c72286248e6de0be0ec9.woff2 +0 -0
  874. package/src/styles/fonts/noto-serif-jp/700/2c8c55e4cec85ce77e95cac9d330f5cf.woff2 +0 -0
  875. package/src/styles/fonts/noto-serif-jp/700/2ed9981d2e8983365bd051159b976b6d.woff2 +0 -0
  876. package/src/styles/fonts/noto-serif-jp/700/2f4c633e923ba30c6ba376367379fc91.woff2 +0 -0
  877. package/src/styles/fonts/noto-serif-jp/700/349965eee0a9b6c984a319ab96a4ece9.woff2 +0 -0
  878. package/src/styles/fonts/noto-serif-jp/700/36cda3eae13370b934bba4429fab9078.woff2 +0 -0
  879. package/src/styles/fonts/noto-serif-jp/700/3aa13f1843d7c8fa16109b6395a9a29d.woff2 +0 -0
  880. package/src/styles/fonts/noto-serif-jp/700/3e2c06bdd9dcab29aeaeb0cfb7fa608a.woff2 +0 -0
  881. package/src/styles/fonts/noto-serif-jp/700/424123f9371ba357087363263eeafcda.woff2 +0 -0
  882. package/src/styles/fonts/noto-serif-jp/700/43b3bbf43055c3e65db9cdf2c5b18c1c.woff2 +0 -0
  883. package/src/styles/fonts/noto-serif-jp/700/44704471b60c18b3ecec5300a88f1102.woff2 +0 -0
  884. package/src/styles/fonts/noto-serif-jp/700/46604090efbb99c2db1800928af0a239.woff2 +0 -0
  885. package/src/styles/fonts/noto-serif-jp/700/46998df85d31e629c0142f0556f5e7c5.woff2 +0 -0
  886. package/src/styles/fonts/noto-serif-jp/700/49902f1c41b281f8eb3771abdd4dccb8.woff2 +0 -0
  887. package/src/styles/fonts/noto-serif-jp/700/4db4f31a16965baa42cb7dad667a8f04.woff2 +0 -0
  888. package/src/styles/fonts/noto-serif-jp/700/572b9e28f1e6d89b765a16d809db84a1.woff2 +0 -0
  889. package/src/styles/fonts/noto-serif-jp/700/5921cef36750ff6092d94b83a802823b.woff2 +0 -0
  890. package/src/styles/fonts/noto-serif-jp/700/59e95d8b5332dcdae894a19946ab767d.woff2 +0 -0
  891. package/src/styles/fonts/noto-serif-jp/700/5ec349ff62b9ee5fe0c6dae867704dc4.woff2 +0 -0
  892. package/src/styles/fonts/noto-serif-jp/700/601f812e8b1eafecd5ba585a6a6d7962.woff2 +0 -0
  893. package/src/styles/fonts/noto-serif-jp/700/6025ddaf99d86cc8bfd781006b19fbd6.woff2 +0 -0
  894. package/src/styles/fonts/noto-serif-jp/700/60f6e28b3f0400b0e8fe32c3c7116102.woff2 +0 -0
  895. package/src/styles/fonts/noto-serif-jp/700/64a404d675f1d726f0891b7a80794595.woff2 +0 -0
  896. package/src/styles/fonts/noto-serif-jp/700/7158022889c6c177062ac85036e7af10.woff2 +0 -0
  897. package/src/styles/fonts/noto-serif-jp/700/7e38ca789a9e76ac91d9256374e154f0.woff2 +0 -0
  898. package/src/styles/fonts/noto-serif-jp/700/815a0b797465190f60d8b1a04656c42e.woff2 +0 -0
  899. package/src/styles/fonts/noto-serif-jp/700/8726558cf297cbda831cc19514897205.woff2 +0 -0
  900. package/src/styles/fonts/noto-serif-jp/700/8855472f3c02f6c7ebb3216617ebe4af.woff2 +0 -0
  901. package/src/styles/fonts/noto-serif-jp/700/8e231d707f0c4f8e3cde90a6b52a79aa.woff2 +0 -0
  902. package/src/styles/fonts/noto-serif-jp/700/9064c12fd72c7aba235d8c3881755b91.woff2 +0 -0
  903. package/src/styles/fonts/noto-serif-jp/700/911b9e53e9814de2998c60bf3115f560.woff2 +0 -0
  904. package/src/styles/fonts/noto-serif-jp/700/92fdc376bce277874e75db666f175b44.woff2 +0 -0
  905. package/src/styles/fonts/noto-serif-jp/700/930a23430f0eb64480d7fe5f82834e21.woff2 +0 -0
  906. package/src/styles/fonts/noto-serif-jp/700/93bed0e5f2dd5a21cf73304fcfbc149d.woff2 +0 -0
  907. package/src/styles/fonts/noto-serif-jp/700/93f3ea6918533d96d9c252378b9a4af0.woff2 +0 -0
  908. package/src/styles/fonts/noto-serif-jp/700/95a0951d2a2722ff773a1a45e8c474d6.woff2 +0 -0
  909. package/src/styles/fonts/noto-serif-jp/700/982cd1765ca6bbb3e6547e47834d5148.woff2 +0 -0
  910. package/src/styles/fonts/noto-serif-jp/700/9b0c3caac458c7bc9c9133e40405ddea.woff2 +0 -0
  911. package/src/styles/fonts/noto-serif-jp/700/9f3bdab4025dc7c5ba1a5871e62e8918.woff2 +0 -0
  912. package/src/styles/fonts/noto-serif-jp/700/a00e94d4f04df6aa659db9c4954c7efe.woff2 +0 -0
  913. package/src/styles/fonts/noto-serif-jp/700/a2b0597837e382aca19919c4b74e58c8.woff2 +0 -0
  914. package/src/styles/fonts/noto-serif-jp/700/a373e85c96aa0dc303092429fb837e51.woff2 +0 -0
  915. package/src/styles/fonts/noto-serif-jp/700/a6e044424780a57610833cc856c15bf5.woff2 +0 -0
  916. package/src/styles/fonts/noto-serif-jp/700/a7c58070d68dc1724e9d4e781afb78db.woff2 +0 -0
  917. package/src/styles/fonts/noto-serif-jp/700/accc43762ad05edfbff66ba1380d192a.woff2 +0 -0
  918. package/src/styles/fonts/noto-serif-jp/700/ad47bcd6d4663026d648c132d969318d.woff2 +0 -0
  919. package/src/styles/fonts/noto-serif-jp/700/af57a2e8c72f9ba0efb1af771d32c124.woff2 +0 -0
  920. package/src/styles/fonts/noto-serif-jp/700/b0ae5f374e43dac992a4a5d334cebc0b.woff2 +0 -0
  921. package/src/styles/fonts/noto-serif-jp/700/b91234c10fd8b8c8abc88e03afe66a1f.woff2 +0 -0
  922. package/src/styles/fonts/noto-serif-jp/700/b9317198f118a1dfd8ddf2b82ec028f3.woff2 +0 -0
  923. package/src/styles/fonts/noto-serif-jp/700/ba825ae79b1a6f7e0cce5215fcb5c96f.woff2 +0 -0
  924. package/src/styles/fonts/noto-serif-jp/700/bc6183ac08f0fac78c46f80c10cf7c92.woff2 +0 -0
  925. package/src/styles/fonts/noto-serif-jp/700/bc8ccea5abf6598cf3cfa97eb59804bb.woff2 +0 -0
  926. package/src/styles/fonts/noto-serif-jp/700/bc95a792cbca6639214c9b0da13392ff.woff2 +0 -0
  927. package/src/styles/fonts/noto-serif-jp/700/bcfa62f35731856246c146d3a6932bf3.woff2 +0 -0
  928. package/src/styles/fonts/noto-serif-jp/700/c063897793f593eb26d6ff7b7baaba18.woff2 +0 -0
  929. package/src/styles/fonts/noto-serif-jp/700/c6c2971ad1f3221f6cf84028aa0f477e.woff2 +0 -0
  930. package/src/styles/fonts/noto-serif-jp/700/c97f41eef722121d86f55d553c056a39.woff2 +0 -0
  931. package/src/styles/fonts/noto-serif-jp/700/ca62704509932d3232d62918de97af3f.woff2 +0 -0
  932. package/src/styles/fonts/noto-serif-jp/700/d1f064825fa5784b5c930652bd831cce.woff2 +0 -0
  933. package/src/styles/fonts/noto-serif-jp/700/d2faabcedd19f016e7b21bce073c0ec8.woff2 +0 -0
  934. package/src/styles/fonts/noto-serif-jp/700/d320171e57480510f87dbbc7d5264b0a.woff2 +0 -0
  935. package/src/styles/fonts/noto-serif-jp/700/d3a72a99d365dddfbca8d017a8011368.woff2 +0 -0
  936. package/src/styles/fonts/noto-serif-jp/700/d42aafa0f246ad3b4c16fe96d3a3a432.woff2 +0 -0
  937. package/src/styles/fonts/noto-serif-jp/700/d4aaf23c13ae808a4bed617afd13aa07.woff2 +0 -0
  938. package/src/styles/fonts/noto-serif-jp/700/d6bb686bddfbe8f38a36a68e609a8667.woff2 +0 -0
  939. package/src/styles/fonts/noto-serif-jp/700/d9020ff69a83b2a6ee1f42ae480f7db0.woff2 +0 -0
  940. package/src/styles/fonts/noto-serif-jp/700/da90a9012ab2d98f759e3fa0820ef502.woff2 +0 -0
  941. package/src/styles/fonts/noto-serif-jp/700/df10d94bea357a43313e20da5d84bd30.woff2 +0 -0
  942. package/src/styles/fonts/noto-serif-jp/700/e34b2b141e472dc776c86fdf8eea23b0.woff2 +0 -0
  943. package/src/styles/fonts/noto-serif-jp/700/e444fd88f1390636e603d0d681538ac8.woff2 +0 -0
  944. package/src/styles/fonts/noto-serif-jp/700/e79898628283edc27180fc39d0d769c1.woff2 +0 -0
  945. package/src/styles/fonts/noto-serif-jp/700/eaf332445a40942928e5f5750c1c3116.woff2 +0 -0
  946. package/src/styles/fonts/noto-serif-jp/700/f91cd722855f4269256eae1187df64ec.woff2 +0 -0
  947. package/src/styles/fonts/noto-serif-jp/700/fc895f5ce66b656f4a933097bf2a8775.woff2 +0 -0
  948. package/src/styles/fonts/noto-serif-jp/700/fca6720fd14c467d29a90f18ef3859b9.woff2 +0 -0
  949. package/src/styles/fonts/noto-serif-jp/700/fd1bb507bcbf04856eeb7f7fd47ea579.woff2 +0 -0
  950. package/src/styles/fonts/noto-serif-jp/noto-serif-jp.css +3349 -0
  951. package/src/styles/fonts/noto-serif-kr/400/033466ef683afe931f7f520cfb42d928.woff2 +0 -0
  952. package/src/styles/fonts/noto-serif-kr/400/05da12edb9d52210581dc6ec4541031f.woff2 +0 -0
  953. package/src/styles/fonts/noto-serif-kr/400/0968e4861204b51f62a2f8e9f15dd5e0.woff2 +0 -0
  954. package/src/styles/fonts/noto-serif-kr/400/0d5dec931dc885f07fe5cd5af8bed675.woff2 +0 -0
  955. package/src/styles/fonts/noto-serif-kr/400/1076f0f6f66d28d7a2f16427faad4413.woff2 +0 -0
  956. package/src/styles/fonts/noto-serif-kr/400/12ef1ba76bd20b004b865266a1aa70b3.woff2 +0 -0
  957. package/src/styles/fonts/noto-serif-kr/400/152395634a207579552f8cb54db88599.woff2 +0 -0
  958. package/src/styles/fonts/noto-serif-kr/400/1a08931435f885e707edb85978ea3bb6.woff2 +0 -0
  959. package/src/styles/fonts/noto-serif-kr/400/31a197213ae61d7043c81013f52d10d6.woff2 +0 -0
  960. package/src/styles/fonts/noto-serif-kr/400/37dbd564820cce2f7b3331e442da0df3.woff2 +0 -0
  961. package/src/styles/fonts/noto-serif-kr/400/37dc8505b20b37a2477a9121b16d44a7.woff2 +0 -0
  962. package/src/styles/fonts/noto-serif-kr/400/3ac0a07878cacdc98bb889f40f5970d3.woff2 +0 -0
  963. package/src/styles/fonts/noto-serif-kr/400/491bbba374093fff045f55803a22bfef.woff2 +0 -0
  964. package/src/styles/fonts/noto-serif-kr/400/4d38e56738156625329d93bd5c8cc74a.woff2 +0 -0
  965. package/src/styles/fonts/noto-serif-kr/400/57e9a86651366c1ba299e47aa7e358c2.woff2 +0 -0
  966. package/src/styles/fonts/noto-serif-kr/400/57f89adeae55aa7fe2add3fc1801ce03.woff2 +0 -0
  967. package/src/styles/fonts/noto-serif-kr/400/5df9ce98c75f8c50fb4fff7f01a23925.woff2 +0 -0
  968. package/src/styles/fonts/noto-serif-kr/400/60020a830d809c26970cf8e48ba1eeda.woff2 +0 -0
  969. package/src/styles/fonts/noto-serif-kr/400/60bcfa3ea7446eb42394aab02d28be40.woff2 +0 -0
  970. package/src/styles/fonts/noto-serif-kr/400/63fafcb069520613d0ea35ad3e6b1e42.woff2 +0 -0
  971. package/src/styles/fonts/noto-serif-kr/400/65a60d87c64228d258a123cbe85f5f31.woff2 +0 -0
  972. package/src/styles/fonts/noto-serif-kr/400/6968e889e18891d912809fe484c2e745.woff2 +0 -0
  973. package/src/styles/fonts/noto-serif-kr/400/70714891ad3fbfc3d5f10a8669dacc5a.woff2 +0 -0
  974. package/src/styles/fonts/noto-serif-kr/400/761d84afca8d7f34eebefe538adba827.woff2 +0 -0
  975. package/src/styles/fonts/noto-serif-kr/400/76e07530323418ec723c5d7634c9bcca.woff2 +0 -0
  976. package/src/styles/fonts/noto-serif-kr/400/773819b7b9b8fd404a929867c0fd677e.woff2 +0 -0
  977. package/src/styles/fonts/noto-serif-kr/400/7817dd16805145d8538ad57590f69f5a.woff2 +0 -0
  978. package/src/styles/fonts/noto-serif-kr/400/7b59f0ec7792b18458dc5a361e37884c.woff2 +0 -0
  979. package/src/styles/fonts/noto-serif-kr/400/7c3945788a689a69356c1a622d69d48b.woff2 +0 -0
  980. package/src/styles/fonts/noto-serif-kr/400/835b6505bb9eea9678925a1fa885353d.woff2 +0 -0
  981. package/src/styles/fonts/noto-serif-kr/400/8b5c9ef81159f31d2ab35f45ca4373e0.woff2 +0 -0
  982. package/src/styles/fonts/noto-serif-kr/400/8b91c7c2ed390f1278b9befa3aa87233.woff2 +0 -0
  983. package/src/styles/fonts/noto-serif-kr/400/8fb45117a62d92dce44a80f0b729ead5.woff2 +0 -0
  984. package/src/styles/fonts/noto-serif-kr/400/90b7848e9b1623b77bdcf155e90b839c.woff2 +0 -0
  985. package/src/styles/fonts/noto-serif-kr/400/985d4d41afa0934a4eb2de35473fb9a2.woff2 +0 -0
  986. package/src/styles/fonts/noto-serif-kr/400/9bb38fd05201de666b88b82d56a386f7.woff2 +0 -0
  987. package/src/styles/fonts/noto-serif-kr/400/9eff2ab2a9d2ced47ab761bf6fdb11ec.woff2 +0 -0
  988. package/src/styles/fonts/noto-serif-kr/400/9f1963fe8d4d6878d717d872a8f3fdb5.woff2 +0 -0
  989. package/src/styles/fonts/noto-serif-kr/400/a0d00fe4816c95a8c7dffd445ef00b03.woff2 +0 -0
  990. package/src/styles/fonts/noto-serif-kr/400/a0f199077fa1a33bc2a1e01c64de7fe6.woff2 +0 -0
  991. package/src/styles/fonts/noto-serif-kr/400/a7c35e42a659347e490c3cb7983b2e7f.woff2 +0 -0
  992. package/src/styles/fonts/noto-serif-kr/400/aa662092cf249707123ca4d575e2764b.woff2 +0 -0
  993. package/src/styles/fonts/noto-serif-kr/400/aae1eda193285ab817a2d1b408440326.woff2 +0 -0
  994. package/src/styles/fonts/noto-serif-kr/400/b0cb664cb2e1371efda8943c0b7dcd1c.woff2 +0 -0
  995. package/src/styles/fonts/noto-serif-kr/400/b0d1cdced482352cf0d3ae58638aacb9.woff2 +0 -0
  996. package/src/styles/fonts/noto-serif-kr/400/b320f5d185b2cff933ac549c184031c5.woff2 +0 -0
  997. package/src/styles/fonts/noto-serif-kr/400/ba9c59d7dfa4494db1bb764ada81467d.woff2 +0 -0
  998. package/src/styles/fonts/noto-serif-kr/400/bab547459d514f46206e340c4bb2dc88.woff2 +0 -0
  999. package/src/styles/fonts/noto-serif-kr/400/bcc39eda837bb7a7a3d37c8c60fffb81.woff2 +0 -0
  1000. package/src/styles/fonts/noto-serif-kr/400/bd73264d7f98776708d5d6f3c9b78fcc.woff2 +0 -0
  1001. package/src/styles/fonts/noto-serif-kr/400/bed610b217d500f5975cfc9fe6157570.woff2 +0 -0
  1002. package/src/styles/fonts/noto-serif-kr/400/bf8901f8f11d4f433ea17dabc9370ea6.woff2 +0 -0
  1003. package/src/styles/fonts/noto-serif-kr/400/c4143bb9f2fe77b6ccf20088a8904650.woff2 +0 -0
  1004. package/src/styles/fonts/noto-serif-kr/400/c7ff3f6bbdcd5f604b7343602ab904df.woff2 +0 -0
  1005. package/src/styles/fonts/noto-serif-kr/400/c945c62368357d05a53206620460fb30.woff2 +0 -0
  1006. package/src/styles/fonts/noto-serif-kr/400/ce022e18a1377ac509443c3c3790b431.woff2 +0 -0
  1007. package/src/styles/fonts/noto-serif-kr/400/cf9cffe56636322f62b40d61130fbc5e.woff2 +0 -0
  1008. package/src/styles/fonts/noto-serif-kr/400/d20d8944bc0b85f5b2aae4b24f343516.woff2 +0 -0
  1009. package/src/styles/fonts/noto-serif-kr/400/d36644d6502527e1fff205d0c7eca434.woff2 +0 -0
  1010. package/src/styles/fonts/noto-serif-kr/400/d40ab99c7a38026f411c8f112f742b48.woff2 +0 -0
  1011. package/src/styles/fonts/noto-serif-kr/400/dc6e234ded795e91f76d6647f628fbf0.woff2 +0 -0
  1012. package/src/styles/fonts/noto-serif-kr/400/dd0cfdfdac0866e66d587e2b5a9e9961.woff2 +0 -0
  1013. package/src/styles/fonts/noto-serif-kr/400/de304c8a02e45ded4f8dcc479d167198.woff2 +0 -0
  1014. package/src/styles/fonts/noto-serif-kr/400/e8c364c16daa04835bf32d293d2598db.woff2 +0 -0
  1015. package/src/styles/fonts/noto-serif-kr/400/ec86e23683052da5cfdc3b77641bd15a.woff2 +0 -0
  1016. package/src/styles/fonts/noto-serif-kr/400/eda1b0cb6d1719dd9bedcf3216a9e8de.woff2 +0 -0
  1017. package/src/styles/fonts/noto-serif-kr/400/ee6fce20b420a480714607c66d7f97e5.woff2 +0 -0
  1018. package/src/styles/fonts/noto-serif-kr/400/eee3836d6ac17ebb2c450bbcbc9db121.woff2 +0 -0
  1019. package/src/styles/fonts/noto-serif-kr/400/f5738255e92d8dd34a46d1bcdf4c4074.woff2 +0 -0
  1020. package/src/styles/fonts/noto-serif-kr/400/fa6e58ce4b52695e7ae19bbea6336ec8.woff2 +0 -0
  1021. package/src/styles/fonts/noto-serif-kr/400/fd2069e6e8588c70b4f11364093b81f2.woff2 +0 -0
  1022. package/src/styles/fonts/noto-serif-kr/700/04b8bde59cff68eeee74fb1914eb09c7.woff2 +0 -0
  1023. package/src/styles/fonts/noto-serif-kr/700/05ac821472e235943ed1435e4bb8ecad.woff2 +0 -0
  1024. package/src/styles/fonts/noto-serif-kr/700/074c1f8483d5a4d8c45c8c5f3e4cbb32.woff2 +0 -0
  1025. package/src/styles/fonts/noto-serif-kr/700/1380210a722ac9aeef54005ad7b015c9.woff2 +0 -0
  1026. package/src/styles/fonts/noto-serif-kr/700/17c7f5d0a45e92ede0e5dec3a2c77efa.woff2 +0 -0
  1027. package/src/styles/fonts/noto-serif-kr/700/1be602ad456b0d75902d352116cb35fd.woff2 +0 -0
  1028. package/src/styles/fonts/noto-serif-kr/700/2612c60f9dc5459ac42763591240a1d6.woff2 +0 -0
  1029. package/src/styles/fonts/noto-serif-kr/700/2ab8cf1f23a5ac7939a7876054e711a7.woff2 +0 -0
  1030. package/src/styles/fonts/noto-serif-kr/700/34e6750e00c3a911ef87dc66b336594d.woff2 +0 -0
  1031. package/src/styles/fonts/noto-serif-kr/700/36f3df4730cfca4fc77fe52b52e6a126.woff2 +0 -0
  1032. package/src/styles/fonts/noto-serif-kr/700/37887deb7a9c466cf2af6ee7ff372ea6.woff2 +0 -0
  1033. package/src/styles/fonts/noto-serif-kr/700/3aba89c4d4281553078de4622b498cbb.woff2 +0 -0
  1034. package/src/styles/fonts/noto-serif-kr/700/3d88558097559d775231194d43745ba7.woff2 +0 -0
  1035. package/src/styles/fonts/noto-serif-kr/700/3dc3e8c35524cad2d1236f3393104ccf.woff2 +0 -0
  1036. package/src/styles/fonts/noto-serif-kr/700/45140742dfa8686b58db2899ce89f89a.woff2 +0 -0
  1037. package/src/styles/fonts/noto-serif-kr/700/469d2754e315e77270c12fa0ab28026c.woff2 +0 -0
  1038. package/src/styles/fonts/noto-serif-kr/700/4c0760284569b87d8f2290c324953973.woff2 +0 -0
  1039. package/src/styles/fonts/noto-serif-kr/700/4cba92c942694bdc479bc54101bb1aa4.woff2 +0 -0
  1040. package/src/styles/fonts/noto-serif-kr/700/4e7d263c30bb48604069a3b4404d4eeb.woff2 +0 -0
  1041. package/src/styles/fonts/noto-serif-kr/700/5279c7e465d9871edf51fd02e691eee7.woff2 +0 -0
  1042. package/src/styles/fonts/noto-serif-kr/700/54bfdb6f21f9d6191e5100580239e14f.woff2 +0 -0
  1043. package/src/styles/fonts/noto-serif-kr/700/56be5e32fd01668fe5ba814d94905839.woff2 +0 -0
  1044. package/src/styles/fonts/noto-serif-kr/700/615b38e0b5bb3ea68426f44f47706e6f.woff2 +0 -0
  1045. package/src/styles/fonts/noto-serif-kr/700/65e9c4585d71bf48a5c62f367010da16.woff2 +0 -0
  1046. package/src/styles/fonts/noto-serif-kr/700/6b75213eb0be40ce84241eb2bb438a2e.woff2 +0 -0
  1047. package/src/styles/fonts/noto-serif-kr/700/7520ca6ca8c60eb1e62d50e71c8d30f1.woff2 +0 -0
  1048. package/src/styles/fonts/noto-serif-kr/700/759a647b77791b1e98f99bc0ab5317a7.woff2 +0 -0
  1049. package/src/styles/fonts/noto-serif-kr/700/76e51630143b95b6322ef93ad330da7a.woff2 +0 -0
  1050. package/src/styles/fonts/noto-serif-kr/700/79aa7c8c842c4a27cf57c0a3a1ffca5a.woff2 +0 -0
  1051. package/src/styles/fonts/noto-serif-kr/700/7cc72fd2c9105560422b6a67c6162945.woff2 +0 -0
  1052. package/src/styles/fonts/noto-serif-kr/700/7d0ea0690e432462f4d05a23d720ec19.woff2 +0 -0
  1053. package/src/styles/fonts/noto-serif-kr/700/7dffa5c1bec57e0903fd62357401ff1a.woff2 +0 -0
  1054. package/src/styles/fonts/noto-serif-kr/700/82f6ccc063960eed1cdfd1d61ada8862.woff2 +0 -0
  1055. package/src/styles/fonts/noto-serif-kr/700/845b4b67564d62cf5cad242f7ac08ea5.woff2 +0 -0
  1056. package/src/styles/fonts/noto-serif-kr/700/84c8e7bc0931008ed91f44ed12dfea94.woff2 +0 -0
  1057. package/src/styles/fonts/noto-serif-kr/700/887a11290ba78b1e66c6d2f67043005e.woff2 +0 -0
  1058. package/src/styles/fonts/noto-serif-kr/700/88db1d042074fb6e66821ffc10941930.woff2 +0 -0
  1059. package/src/styles/fonts/noto-serif-kr/700/8dc18c0cebe6aa4bf4c45dbb831048ab.woff2 +0 -0
  1060. package/src/styles/fonts/noto-serif-kr/700/919a879bd2d580d8491a31a449390689.woff2 +0 -0
  1061. package/src/styles/fonts/noto-serif-kr/700/91da6cb174bebfb96976e2f1316be2fe.woff2 +0 -0
  1062. package/src/styles/fonts/noto-serif-kr/700/934dfdbed2bb2c4b6129199d699a34fa.woff2 +0 -0
  1063. package/src/styles/fonts/noto-serif-kr/700/97d7a17234f2b5030ae9697ae00aded2.woff2 +0 -0
  1064. package/src/styles/fonts/noto-serif-kr/700/995c4fda62d25c3b79dd5987737df6ae.woff2 +0 -0
  1065. package/src/styles/fonts/noto-serif-kr/700/9d0afa53dc2f92ba42024f55f11f6779.woff2 +0 -0
  1066. package/src/styles/fonts/noto-serif-kr/700/9e13a47242926f1cd7d68c08d9ef8889.woff2 +0 -0
  1067. package/src/styles/fonts/noto-serif-kr/700/a379ada89abd750c587ded29f77e731c.woff2 +0 -0
  1068. package/src/styles/fonts/noto-serif-kr/700/a48be56ce5d3a99dc8f8331398004412.woff2 +0 -0
  1069. package/src/styles/fonts/noto-serif-kr/700/a806759e72987951d6d08b7cbdf36a1b.woff2 +0 -0
  1070. package/src/styles/fonts/noto-serif-kr/700/a88481ec8bc7be11cb66e46781a79bb9.woff2 +0 -0
  1071. package/src/styles/fonts/noto-serif-kr/700/a9a016d4a93409f65278327e8a8bb38d.woff2 +0 -0
  1072. package/src/styles/fonts/noto-serif-kr/700/ab92d41062a7644faa45f50bf384454a.woff2 +0 -0
  1073. package/src/styles/fonts/noto-serif-kr/700/b326a8a9c33b554db570da94f60bc380.woff2 +0 -0
  1074. package/src/styles/fonts/noto-serif-kr/700/b6117ff2993b11bb1fdc7ea3588a010c.woff2 +0 -0
  1075. package/src/styles/fonts/noto-serif-kr/700/b61982951bd51b724143c30dfaaa9fe9.woff2 +0 -0
  1076. package/src/styles/fonts/noto-serif-kr/700/c0098958e20db68cab90097b5e62516f.woff2 +0 -0
  1077. package/src/styles/fonts/noto-serif-kr/700/c4bfaa5e50798246e3770718b7a7c84a.woff2 +0 -0
  1078. package/src/styles/fonts/noto-serif-kr/700/c84598999133455503042e06f4ab79cb.woff2 +0 -0
  1079. package/src/styles/fonts/noto-serif-kr/700/ca9a533988d7019597a60d4e17127e0c.woff2 +0 -0
  1080. package/src/styles/fonts/noto-serif-kr/700/cf2b28f90f47276f7e2688a65e88a101.woff2 +0 -0
  1081. package/src/styles/fonts/noto-serif-kr/700/d689b1861d7e4377dd72ad3013482612.woff2 +0 -0
  1082. package/src/styles/fonts/noto-serif-kr/700/d9047070d72a816b3dba9d40c2d85e69.woff2 +0 -0
  1083. package/src/styles/fonts/noto-serif-kr/700/da04549f3f4ed28076b01b8cd710d313.woff2 +0 -0
  1084. package/src/styles/fonts/noto-serif-kr/700/da7af303f8c645f9a9dbae0e6e32dd35.woff2 +0 -0
  1085. package/src/styles/fonts/noto-serif-kr/700/df9568257eb29b156449fdd4bec5ec76.woff2 +0 -0
  1086. package/src/styles/fonts/noto-serif-kr/700/e067cd0ed76c90cd0a93c9339253f20b.woff2 +0 -0
  1087. package/src/styles/fonts/noto-serif-kr/700/e08b07772e7bed3cec2832d43f7fd339.woff2 +0 -0
  1088. package/src/styles/fonts/noto-serif-kr/700/e12150d5a39b30be8f567968c7a527b0.woff2 +0 -0
  1089. package/src/styles/fonts/noto-serif-kr/700/e53fcb2381eee345db4f6f973dd95a3e.woff2 +0 -0
  1090. package/src/styles/fonts/noto-serif-kr/700/e5bd313ef81f687d398aacb11cec3069.woff2 +0 -0
  1091. package/src/styles/fonts/noto-serif-kr/700/eb5afb3d952b8593782caec6026514b6.woff2 +0 -0
  1092. package/src/styles/fonts/noto-serif-kr/700/edd6a4f608d04fc0351d7688cfc321e4.woff2 +0 -0
  1093. package/src/styles/fonts/noto-serif-kr/700/f9695c6c4df2bf6bc03045ff79d4f01f.woff2 +0 -0
  1094. package/src/styles/fonts/noto-serif-kr/700/fada6eaa68ff8816afe43d2a36c5423e.woff2 +0 -0
  1095. package/src/styles/fonts/noto-serif-kr/700/fbcc4bf5367218951172bdee6f77d7a6.woff2 +0 -0
  1096. package/src/styles/fonts/noto-serif-kr/noto-serif-kr.css +2601 -0
  1097. package/src/styles/site-media.css +823 -0
  1098. package/src/styles/tokens.css +292 -21
  1099. package/src/styles/ui.css +4188 -1857
  1100. package/src/types/app-context.ts +20 -0
  1101. package/src/types/bindings.ts +3 -1
  1102. package/src/types/config.ts +147 -9
  1103. package/src/types/constants.ts +46 -11
  1104. package/src/types/entities.ts +24 -8
  1105. package/src/types/operations.ts +80 -1
  1106. package/src/types/props.ts +10 -9
  1107. package/src/types/raw-assets.d.ts +39 -0
  1108. package/src/types/views.ts +28 -7
  1109. package/src/ui/__tests__/color-themes.test.ts +7 -40
  1110. package/src/ui/__tests__/font-themes.test.ts +30 -11
  1111. package/src/ui/color-themes.ts +311 -270
  1112. package/src/ui/compose/ComposeDialog.tsx +712 -394
  1113. package/src/ui/compose/ComposePrompt.tsx +15 -10
  1114. package/src/ui/dash/ActionButtons.tsx +27 -18
  1115. package/src/ui/dash/DangerZone.tsx +15 -10
  1116. package/src/ui/dash/FormatBadge.tsx +21 -8
  1117. package/src/ui/dash/StatusBadge.tsx +33 -22
  1118. package/src/ui/dash/appearance/AdvancedContent.tsx +44 -30
  1119. package/src/ui/dash/appearance/CodeInjectionContent.tsx +164 -0
  1120. package/src/ui/dash/appearance/ColorThemeContent.tsx +127 -160
  1121. package/src/ui/dash/appearance/FontThemeContent.tsx +20 -15
  1122. package/src/ui/dash/appearance/NavigationContent.tsx +368 -225
  1123. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +54 -0
  1124. package/src/ui/dash/settings/AccountContent.tsx +34 -23
  1125. package/src/ui/dash/settings/AccountMenuContent.tsx +226 -137
  1126. package/src/ui/dash/settings/ApiTokensContent.tsx +122 -79
  1127. package/src/ui/dash/settings/AvatarContent.tsx +73 -45
  1128. package/src/ui/dash/settings/DeleteAccountContent.tsx +106 -66
  1129. package/src/ui/dash/settings/GeneralContent.tsx +274 -157
  1130. package/src/ui/dash/settings/GitHubSyncContent.tsx +558 -0
  1131. package/src/ui/dash/settings/SessionsContent.tsx +79 -38
  1132. package/src/ui/dash/settings/SettingsRootContent.tsx +209 -109
  1133. package/src/ui/dash/settings/__tests__/AccountMenuContent.test.tsx +67 -0
  1134. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +75 -0
  1135. package/src/ui/feed/CuratedThreadPreview.tsx +24 -14
  1136. package/src/ui/feed/LinkCard.tsx +116 -84
  1137. package/src/ui/feed/LinkPreview.tsx +75 -0
  1138. package/src/ui/feed/NoteCard.tsx +60 -16
  1139. package/src/ui/feed/PostStatusBadges.tsx +15 -0
  1140. package/src/ui/feed/QuoteCard.tsx +10 -3
  1141. package/src/ui/feed/ThreadPreview.tsx +62 -36
  1142. package/src/ui/feed/TimelineFeed.tsx +2 -1
  1143. package/src/ui/feed/__tests__/thread-preview.test.ts +220 -11
  1144. package/src/ui/feed/__tests__/timeline-cards.test.ts +186 -5
  1145. package/src/ui/feed/thread-preview-state.ts +23 -10
  1146. package/src/ui/font-themes.ts +57 -97
  1147. package/src/ui/layouts/BaseLayout.tsx +230 -136
  1148. package/src/ui/layouts/SiteLayout.tsx +431 -143
  1149. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +76 -6
  1150. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +76 -0
  1151. package/src/ui/pages/ArchivePage.tsx +320 -207
  1152. package/src/ui/pages/BrandPage.tsx +499 -341
  1153. package/src/ui/pages/CollectionEditorPage.tsx +50 -30
  1154. package/src/ui/pages/CollectionPage.tsx +151 -78
  1155. package/src/ui/pages/CollectionsPage.tsx +27 -18
  1156. package/src/ui/pages/ComposePage.tsx +15 -10
  1157. package/src/ui/pages/FeaturedPage.tsx +16 -11
  1158. package/src/ui/pages/HomePage.tsx +15 -10
  1159. package/src/ui/pages/PostPage.tsx +5 -1
  1160. package/src/ui/pages/SearchPage.tsx +93 -43
  1161. package/src/ui/pages/ThemeSamplePage.tsx +758 -520
  1162. package/src/ui/pages/__tests__/ArchivePage.test.tsx +72 -0
  1163. package/src/ui/shared/CollectionDirectory.tsx +215 -34
  1164. package/src/ui/shared/CollectionsManager.tsx +155 -75
  1165. package/src/ui/shared/DecorativeQuoteMark.tsx +12 -9
  1166. package/src/ui/shared/MediaGallery.tsx +350 -256
  1167. package/src/ui/shared/Pagination.tsx +36 -25
  1168. package/src/ui/shared/PostFooter.tsx +139 -96
  1169. package/src/ui/shared/__tests__/media-gallery.test.ts +27 -0
  1170. package/src/ui/shared/__tests__/navigation-labels.test.ts +79 -28
  1171. package/src/ui/shared/__tests__/post-footer.test.ts +42 -17
  1172. package/src/ui/shared/collection-management-labels.ts +109 -33
  1173. package/src/ui/shared/navigation-labels.ts +78 -8
  1174. package/bin/lib/site-localize-media.js +0 -427
  1175. package/dist/client/_assets/client-auth.js +0 -3260
  1176. package/dist/client/_assets/client.css +0 -2
  1177. package/dist/client/_assets/client.js +0 -380
  1178. package/src/__tests__/site-localize-media.test.ts +0 -150
  1179. package/src/client/components/jant-collection-sidebar.ts +0 -816
  1180. package/src/i18n/locales/en.ts +0 -1
  1181. package/src/i18n/locales/zh-Hans.po +0 -3197
  1182. package/src/i18n/locales/zh-Hans.ts +0 -1
  1183. package/src/i18n/locales/zh-Hant.po +0 -3197
  1184. package/src/i18n/locales/zh-Hant.ts +0 -1
  1185. package/src/routes/feed/rss.ts +0 -216
  1186. package/src/types/lingui-react-macro.d.ts +0 -34
  1187. /package/dist/client/_assets/chunks/{heic-to-XcUDQvtx.js → heic-to-DUUaO23q.js} +0 -0
  1188. /package/dist/client/_assets/chunks/{module-ChVQstFd.js → module-DcsAZQZ_.js} +0 -0
  1189. /package/dist/client/_assets/chunks/{native-CR5HLOyf.js → native-DpcrFAPh.js} +0 -0
  1190. /package/dist/client/_assets/{client-cjk.css → client-cjk-B7Z0snDu.css} +0 -0
  1191. /package/dist/client/_assets/{client-cjk-tc.css → client-cjk-tc-BesJYrb2.css} +0 -0
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { Hono, type Context } from "hono";
10
+ import { getCookie, setCookie } from "hono/cookie";
10
11
  import { msg } from "@lingui/core/macro";
11
12
  import { z } from "zod";
12
13
  import type { Bindings } from "../../types.js";
@@ -36,8 +37,13 @@ import { NavigationContent } from "../../ui/dash/appearance/NavigationContent.js
36
37
  import { ColorThemeContent } from "../../ui/dash/appearance/ColorThemeContent.js";
37
38
  import { FontThemeContent } from "../../ui/dash/appearance/FontThemeContent.js";
38
39
  import { AdvancedContent } from "../../ui/dash/appearance/AdvancedContent.js";
40
+ import { CodeInjectionContent } from "../../ui/dash/appearance/CodeInjectionContent.js";
39
41
  import { ApiTokensContent } from "../../ui/dash/settings/ApiTokensContent.js";
40
42
  import { DeleteAccountContent } from "../../ui/dash/settings/DeleteAccountContent.js";
43
+ import {
44
+ GitHubSyncContent,
45
+ type GitHubSyncStatus,
46
+ } from "../../ui/dash/settings/GitHubSyncContent.js";
41
47
  import { toAbsoluteSiteUrl, toPublicPath } from "../../lib/url.js";
42
48
  import { parseValidated, UpdateSiteSettingsSchema } from "../../lib/schemas.js";
43
49
  import {
@@ -47,6 +53,32 @@ import {
47
53
  getHostedControlPlaneSiteDeleteUrl,
48
54
  } from "../../lib/hosted-signin.js";
49
55
  import { syncHostedControlPlaneSiteAvatar } from "../../lib/hosted-control-plane-sync.js";
56
+ import {
57
+ getGitHubAppConfig,
58
+ getHostedControlPlaneSsoSecret,
59
+ } from "../../lib/env.js";
60
+ import {
61
+ buildInstallUrl,
62
+ getInstallation,
63
+ listInstallationReposPage,
64
+ searchInstallationRepos,
65
+ } from "../../lib/github-app.js";
66
+ import {
67
+ isSyncPending,
68
+ markSyncPending,
69
+ runBackgroundSync,
70
+ } from "../../lib/github-sync-trigger.js";
71
+ import { buildSyncSiteConfig } from "../../lib/github-sync-site-config.js";
72
+ import {
73
+ readGitHubSyncStatus,
74
+ renderStatusCardHtml,
75
+ getSyncStatusStreamUrl,
76
+ } from "../../lib/github-sync-status.js";
77
+ import {
78
+ generateInstallNonce,
79
+ signInstallState,
80
+ verifyInstallState,
81
+ } from "../../lib/github-app-state.js";
50
82
 
51
83
  type Env = { Bindings: Bindings; Variables: AppVariables };
52
84
 
@@ -54,6 +86,7 @@ export const settingsRoutes = new Hono<Env>();
54
86
 
55
87
  const UpdateLocaleSettingsSchema = z.object({
56
88
  siteLanguage: z.string(),
89
+ cjkSerifFont: z.string(),
57
90
  timeZone: z.string(),
58
91
  });
59
92
 
@@ -73,6 +106,100 @@ function publicPath(c: Context<Env>, path: string): string {
73
106
  return toPublicPath(path, c.var.appConfig.sitePathPrefix);
74
107
  }
75
108
 
109
+ /**
110
+ * Breadcrumb labels for admin settings pages.
111
+ *
112
+ * These duplicate labels defined in the corresponding UI components (e.g.
113
+ * `SettingsRootContent`, `AccountMenuContent`) on purpose: Lingui hashes are
114
+ * computed from `message` alone, so identical text produces the same catalog
115
+ * entry and shares the translation. Keep the text in sync with the UI source
116
+ * of truth.
117
+ */
118
+ function breadcrumbLabel(
119
+ c: Context<Env>,
120
+ key:
121
+ | "settings"
122
+ | "general"
123
+ | "avatar"
124
+ | "navigation"
125
+ | "colorTheme"
126
+ | "fontTheme"
127
+ | "customCss"
128
+ | "codeInjection"
129
+ | "account"
130
+ | "sessions"
131
+ | "password"
132
+ | "deleteAccount"
133
+ | "apiTokens"
134
+ | "githubSync",
135
+ ): string {
136
+ const i18n = getI18n(c);
137
+ switch (key) {
138
+ case "settings":
139
+ return i18n._(
140
+ msg({ message: "Settings", comment: "@context: Breadcrumb label" }),
141
+ );
142
+ case "general":
143
+ return i18n._(
144
+ msg({ message: "General", comment: "@context: Breadcrumb label" }),
145
+ );
146
+ case "avatar":
147
+ return i18n._(
148
+ msg({ message: "Avatar", comment: "@context: Breadcrumb label" }),
149
+ );
150
+ case "navigation":
151
+ return i18n._(
152
+ msg({ message: "Navigation", comment: "@context: Breadcrumb label" }),
153
+ );
154
+ case "colorTheme":
155
+ return i18n._(
156
+ msg({ message: "Color Theme", comment: "@context: Breadcrumb label" }),
157
+ );
158
+ case "fontTheme":
159
+ return i18n._(
160
+ msg({ message: "Font Theme", comment: "@context: Breadcrumb label" }),
161
+ );
162
+ case "customCss":
163
+ return i18n._(
164
+ msg({ message: "Custom CSS", comment: "@context: Breadcrumb label" }),
165
+ );
166
+ case "codeInjection":
167
+ return i18n._(
168
+ msg({
169
+ message: "Code Injection",
170
+ comment: "@context: Breadcrumb label",
171
+ }),
172
+ );
173
+ case "account":
174
+ return i18n._(
175
+ msg({ message: "Account", comment: "@context: Breadcrumb label" }),
176
+ );
177
+ case "sessions":
178
+ return i18n._(
179
+ msg({ message: "Sessions", comment: "@context: Breadcrumb label" }),
180
+ );
181
+ case "password":
182
+ return i18n._(
183
+ msg({ message: "Password", comment: "@context: Breadcrumb label" }),
184
+ );
185
+ case "deleteAccount":
186
+ return i18n._(
187
+ msg({
188
+ message: "Delete Account",
189
+ comment: "@context: Breadcrumb label",
190
+ }),
191
+ );
192
+ case "apiTokens":
193
+ return i18n._(
194
+ msg({ message: "API Tokens", comment: "@context: Breadcrumb label" }),
195
+ );
196
+ case "githubSync":
197
+ return i18n._(
198
+ msg({ message: "GitHub Sync", comment: "@context: Breadcrumb label" }),
199
+ );
200
+ }
201
+ }
202
+
76
203
  type DemoRestriction = "sessions" | "password" | "accountDeletion";
77
204
 
78
205
  function getDemoRestrictionMessage(
@@ -163,14 +290,15 @@ settingsRoutes.get("/general", async (c) => {
163
290
  content: (
164
291
  <>
165
292
  <AdminBreadcrumb
166
- parent="Settings"
293
+ parent={breadcrumbLabel(c, "settings")}
167
294
  parentHref={publicPath(c, "/settings")}
168
- current="General"
295
+ current={breadcrumbLabel(c, "general")}
169
296
  />
170
297
  <GeneralContent
171
298
  siteName={dbSiteName || ""}
172
299
  siteDescription={dbSiteDescription || ""}
173
300
  siteLanguage={appConfig.siteLanguage}
301
+ cjkSerifFont={appConfig.cjkSerifFont}
174
302
  siteNameFallback={appConfig.fallbacks.siteName}
175
303
  siteDescriptionFallback={appConfig.fallbacks.siteDescription}
176
304
  mainRssFeed={appConfig.mainRssFeed}
@@ -277,7 +405,8 @@ settingsRoutes.post("/general/language-time", async (c) => {
277
405
  );
278
406
  const { languageChanged } =
279
407
  await c.var.services.settings.updateLocaleSettings(body, {
280
- oldLanguage: c.var.allSettings["SITE_LANGUAGE"] ?? "en",
408
+ oldLanguage: c.var.appConfig.siteLanguage,
409
+ oldCjkSerifFont: c.var.appConfig.cjkSerifFont,
281
410
  });
282
411
 
283
412
  const wantsJson = c.req.header("accept")?.includes("application/json");
@@ -386,9 +515,9 @@ settingsRoutes.get("/avatar", async (c) => {
386
515
  content: (
387
516
  <>
388
517
  <AdminBreadcrumb
389
- parent="Settings"
518
+ parent={breadcrumbLabel(c, "settings")}
390
519
  parentHref={publicPath(c, "/settings")}
391
- current="Avatar"
520
+ current={breadcrumbLabel(c, "avatar")}
392
521
  />
393
522
  <AvatarContent
394
523
  siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
@@ -576,9 +705,10 @@ settingsRoutes.post("/avatar/display", async (c) => {
576
705
  // ===========================================================================
577
706
 
578
707
  settingsRoutes.get("/navigation", async (c) => {
579
- const navItems = await c.var.services.navItems.list();
580
- const headerNavMaxVisible = c.var.appConfig.headerNavMaxVisible;
581
- const homeDefaultView = c.var.appConfig.homeDefaultView;
708
+ const [navItems, directoryData] = await Promise.all([
709
+ c.var.services.navItems.list(),
710
+ c.var.services.collections.listDirectoryData(),
711
+ ]);
582
712
  const navData = await getNavigationData(c);
583
713
 
584
714
  return renderPublicPage(c, {
@@ -587,14 +717,13 @@ settingsRoutes.get("/navigation", async (c) => {
587
717
  content: (
588
718
  <>
589
719
  <AdminBreadcrumb
590
- parent="Settings"
720
+ parent={breadcrumbLabel(c, "settings")}
591
721
  parentHref={publicPath(c, "/settings")}
592
- current="Navigation"
722
+ current={breadcrumbLabel(c, "navigation")}
593
723
  />
594
724
  <NavigationContent
595
725
  navItems={navItems}
596
- headerNavMaxVisible={headerNavMaxVisible}
597
- homeDefaultView={homeDefaultView}
726
+ directoryData={directoryData}
598
727
  mainRssFeed={c.var.appConfig.mainRssFeed}
599
728
  siteName={navData.siteName}
600
729
  sitePathPrefix={c.var.appConfig.sitePathPrefix}
@@ -604,41 +733,12 @@ settingsRoutes.get("/navigation", async (c) => {
604
733
  });
605
734
  });
606
735
 
607
- settingsRoutes.post("/navigation/nav-max-visible", async (c) => {
608
- const body = await c.req.json<{ value: number }>();
609
- const { settings } = c.var.services;
610
-
611
- const navMax = Math.max(0, Math.min(5, body.value ?? 3));
612
- if (navMax !== 3) {
613
- await settings.set("HEADER_NAV_MAX_VISIBLE", String(navMax));
614
- } else {
615
- await settings.remove("HEADER_NAV_MAX_VISIBLE");
616
- }
617
-
618
- return c.json({ ok: true });
619
- });
620
-
621
- settingsRoutes.post("/navigation/home-default-view", async (c) => {
622
- const body = await c.req.json<{ value: string }>();
623
- const { settings } = c.var.services;
624
-
625
- if (body.value === "featured") {
626
- await settings.set("HOME_DEFAULT_VIEW", "featured");
627
- } else {
628
- await settings.remove("HOME_DEFAULT_VIEW");
629
- }
630
-
631
- return c.json({ ok: true });
632
- });
633
-
634
736
  // ===========================================================================
635
737
  // Color Theme (moved from appearance routes)
636
738
  // ===========================================================================
637
739
 
638
740
  settingsRoutes.get("/color-theme", async (c) => {
639
- const defaultThemeId = c.var.appConfig.fallbacks.defaultTheme;
640
- const currentThemeId =
641
- c.var.allSettings[SETTINGS_KEYS.THEME] ?? defaultThemeId;
741
+ const currentThemeId = c.var.appConfig.themeId;
642
742
  const currentThemeMode = c.var.appConfig.themeMode;
643
743
  const themes = getAvailableThemes();
644
744
  const saved = c.req.query("saved") !== undefined;
@@ -651,9 +751,9 @@ settingsRoutes.get("/color-theme", async (c) => {
651
751
  content: (
652
752
  <>
653
753
  <AdminBreadcrumb
654
- parent="Settings"
754
+ parent={breadcrumbLabel(c, "settings")}
655
755
  parentHref={publicPath(c, "/settings")}
656
- current="Color Theme"
756
+ current={breadcrumbLabel(c, "colorTheme")}
657
757
  />
658
758
  <ColorThemeContent
659
759
  themes={themes}
@@ -685,12 +785,7 @@ settingsRoutes.post("/color-theme", async (c) => {
685
785
  );
686
786
  }
687
787
 
688
- const defaultThemeId = c.var.appConfig.fallbacks.defaultTheme;
689
- if (validTheme.id === defaultThemeId) {
690
- await settings.remove(SETTINGS_KEYS.THEME);
691
- } else {
692
- await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
693
- }
788
+ await settings.set(SETTINGS_KEYS.THEME, validTheme.id);
694
789
 
695
790
  const themeMode: ThemeMode = THEME_MODES.includes(body.themeMode as ThemeMode)
696
791
  ? (body.themeMode as ThemeMode)
@@ -710,7 +805,7 @@ settingsRoutes.post("/color-theme", async (c) => {
710
805
  // ===========================================================================
711
806
 
712
807
  settingsRoutes.get("/font-theme", async (c) => {
713
- const currentFontThemeId = c.var.allSettings["FONT_THEME"] ?? "default";
808
+ const currentFontThemeId = c.var.appConfig.fontThemeId;
714
809
  const saved = c.req.query("saved") !== undefined;
715
810
  const navData = await getNavigationData(c);
716
811
 
@@ -721,9 +816,9 @@ settingsRoutes.get("/font-theme", async (c) => {
721
816
  content: (
722
817
  <>
723
818
  <AdminBreadcrumb
724
- parent="Settings"
819
+ parent={breadcrumbLabel(c, "settings")}
725
820
  parentHref={publicPath(c, "/settings")}
726
- current="Font Theme"
821
+ current={breadcrumbLabel(c, "fontTheme")}
727
822
  />
728
823
  <FontThemeContent
729
824
  fontThemes={BUILTIN_FONT_THEMES}
@@ -754,11 +849,7 @@ settingsRoutes.post("/font-theme", async (c) => {
754
849
  );
755
850
  }
756
851
 
757
- if (validFont.id === "default") {
758
- await settings.remove("FONT_THEME");
759
- } else {
760
- await settings.set("FONT_THEME", validFont.id);
761
- }
852
+ await settings.set("FONT_THEME", validFont.id);
762
853
 
763
854
  return dsRedirect(publicPath(c, "/settings/font-theme?saved"));
764
855
  });
@@ -777,9 +868,9 @@ settingsRoutes.get("/custom-css", async (c) => {
777
868
  content: (
778
869
  <>
779
870
  <AdminBreadcrumb
780
- parent="Settings"
871
+ parent={breadcrumbLabel(c, "settings")}
781
872
  parentHref={publicPath(c, "/settings")}
782
- current="Custom CSS"
873
+ current={breadcrumbLabel(c, "customCss")}
783
874
  />
784
875
  <AdvancedContent
785
876
  customCSS={customCSS}
@@ -813,6 +904,70 @@ settingsRoutes.post("/custom-css", async (c) => {
813
904
  );
814
905
  });
815
906
 
907
+ // ===========================================================================
908
+ // Code Injection — site-wide HTML in <head> and at end of <body>.
909
+ // ===========================================================================
910
+
911
+ settingsRoutes.get("/code-injection", async (c) => {
912
+ const customHeadHtml =
913
+ c.var.allSettings[SETTINGS_KEYS.CUSTOM_HEAD_HTML] ?? "";
914
+ const customBodyEndHtml =
915
+ c.var.allSettings[SETTINGS_KEYS.CUSTOM_BODY_END_HTML] ?? "";
916
+ const navData = await getNavigationData(c);
917
+
918
+ return renderPublicPage(c, {
919
+ title: buildPageTitle("Code Injection", navData.siteName),
920
+ navData,
921
+ content: (
922
+ <>
923
+ <AdminBreadcrumb
924
+ parent={breadcrumbLabel(c, "settings")}
925
+ parentHref={publicPath(c, "/settings")}
926
+ current={breadcrumbLabel(c, "codeInjection")}
927
+ />
928
+ <CodeInjectionContent
929
+ customHeadHtml={customHeadHtml}
930
+ customBodyEndHtml={customBodyEndHtml}
931
+ sitePathPrefix={c.var.appConfig.sitePathPrefix}
932
+ />
933
+ </>
934
+ ),
935
+ });
936
+ });
937
+
938
+ settingsRoutes.post("/code-injection", async (c) => {
939
+ const i18n = getI18n(c);
940
+ const body = await c.req.json<{
941
+ customHeadHtml?: string;
942
+ customBodyEndHtml?: string;
943
+ }>();
944
+ const { settings } = c.var.services;
945
+
946
+ const headHtml = body.customHeadHtml?.trim() ?? "";
947
+ const bodyEndHtml = body.customBodyEndHtml?.trim() ?? "";
948
+
949
+ if (headHtml) {
950
+ await settings.set(SETTINGS_KEYS.CUSTOM_HEAD_HTML, headHtml);
951
+ } else {
952
+ await settings.remove(SETTINGS_KEYS.CUSTOM_HEAD_HTML);
953
+ }
954
+
955
+ if (bodyEndHtml) {
956
+ await settings.set(SETTINGS_KEYS.CUSTOM_BODY_END_HTML, bodyEndHtml);
957
+ } else {
958
+ await settings.remove(SETTINGS_KEYS.CUSTOM_BODY_END_HTML);
959
+ }
960
+
961
+ return dsToast(
962
+ i18n._(
963
+ msg({
964
+ message: "Code injection updated.",
965
+ comment: "@context: Toast after saving Code Injection settings",
966
+ }),
967
+ ),
968
+ );
969
+ });
970
+
816
971
  // ===========================================================================
817
972
  // Account sub-menu
818
973
  // ===========================================================================
@@ -834,9 +989,9 @@ settingsRoutes.get("/account", async (c) => {
834
989
  content: (
835
990
  <>
836
991
  <AdminBreadcrumb
837
- parent="Settings"
992
+ parent={breadcrumbLabel(c, "settings")}
838
993
  parentHref={publicPath(c, "/settings")}
839
- current="Account"
994
+ current={breadcrumbLabel(c, "account")}
840
995
  />
841
996
  <AccountMenuContent
842
997
  sitePathPrefix={c.var.appConfig.sitePathPrefix}
@@ -861,11 +1016,9 @@ settingsRoutes.get("/account/sessions", async (c) => {
861
1016
 
862
1017
  const navData = await getNavigationData(c);
863
1018
 
864
- // Get current session to mark it
865
- const currentSession = await c.var.auth.api.getSession({
866
- headers: c.req.raw.headers,
867
- });
868
- const currentToken = currentSession?.session?.token ?? "";
1019
+ // Session was pre-fetched by `attachSession`; this route is behind
1020
+ // `requireAuth`, so it's guaranteed to be present here.
1021
+ const currentToken = c.var.session?.session?.token ?? "";
869
1022
 
870
1023
  // List all active sessions
871
1024
  const rawSessions = await c.var.auth.api.listSessions({
@@ -900,9 +1053,9 @@ settingsRoutes.get("/account/sessions", async (c) => {
900
1053
  content: (
901
1054
  <>
902
1055
  <AdminBreadcrumb
903
- parent="Account"
1056
+ parent={breadcrumbLabel(c, "account")}
904
1057
  parentHref={publicPath(c, "/settings/account")}
905
- current="Sessions"
1058
+ current={breadcrumbLabel(c, "sessions")}
906
1059
  />
907
1060
  <SessionsContent
908
1061
  sessions={sessions}
@@ -955,9 +1108,9 @@ settingsRoutes.get("/account/password", async (c) => {
955
1108
  content: (
956
1109
  <>
957
1110
  <AdminBreadcrumb
958
- parent="Account"
1111
+ parent={breadcrumbLabel(c, "account")}
959
1112
  parentHref={publicPath(c, "/settings/account")}
960
- current="Password"
1113
+ current={breadcrumbLabel(c, "password")}
961
1114
  />
962
1115
  <AccountContent sitePathPrefix={c.var.appConfig.sitePathPrefix} />
963
1116
  </>
@@ -1066,9 +1219,9 @@ settingsRoutes.get("/account/delete-account", async (c) => {
1066
1219
  content: (
1067
1220
  <>
1068
1221
  <AdminBreadcrumb
1069
- parent="Account"
1222
+ parent={breadcrumbLabel(c, "account")}
1070
1223
  parentHref={publicPath(c, "/settings/account")}
1071
- current="Delete Account"
1224
+ current={breadcrumbLabel(c, "deleteAccount")}
1072
1225
  />
1073
1226
  <DeleteAccountContent
1074
1227
  siteName={navData.siteName}
@@ -1152,9 +1305,9 @@ settingsRoutes.get("/api-tokens", async (c) => {
1152
1305
  content: (
1153
1306
  <>
1154
1307
  <AdminBreadcrumb
1155
- parent="Settings"
1308
+ parent={breadcrumbLabel(c, "settings")}
1156
1309
  parentHref={publicPath(c, "/settings")}
1157
- current="API Tokens"
1310
+ current={breadcrumbLabel(c, "apiTokens")}
1158
1311
  />
1159
1312
  <ApiTokensContent
1160
1313
  tokens={tokens}
@@ -1190,3 +1343,957 @@ settingsRoutes.post("/api-tokens/:id/delete", async (c) => {
1190
1343
 
1191
1344
  return dsRedirect(publicPath(c, "/settings/api-tokens"));
1192
1345
  });
1346
+
1347
+ // ===========================================================================
1348
+ // GitHub Sync
1349
+ // ===========================================================================
1350
+
1351
+ settingsRoutes.post("/github-sync/connect", async (c) => {
1352
+ // When a GitHub App is configured on this deployment, PAT connect is
1353
+ // disabled — users must go through the App install flow so we don't end
1354
+ // up with a mix of auth modes per site (harder to audit, easier to leak
1355
+ // a long-lived token by accident). The UI hides the PAT form too.
1356
+ if (getGitHubAppConfig(c.env)) {
1357
+ return dsToast(
1358
+ "This deployment uses GitHub App authentication. Use Install GitHub App instead.",
1359
+ "error",
1360
+ );
1361
+ }
1362
+
1363
+ const body = await c.req.json<{ token: string; repo: string }>();
1364
+
1365
+ if (!body.token?.trim() || !body.repo?.trim()) {
1366
+ return dsToast("Token and repository are required.", "error");
1367
+ }
1368
+
1369
+ const { parseRepoSlug, createGitHubClient } =
1370
+ await import("../../lib/github-api.js");
1371
+ const parsed = parseRepoSlug(body.repo);
1372
+ if (!parsed) {
1373
+ return dsToast("Invalid repository format. Use owner/repo.", "error");
1374
+ }
1375
+
1376
+ // Validate token
1377
+ const client = createGitHubClient(body.token);
1378
+ try {
1379
+ await client.getRepo(parsed.owner, parsed.repo);
1380
+ } catch (err) {
1381
+ const detail = err instanceof Error ? err.message : JSON.stringify(err);
1382
+ process.stderr.write(
1383
+ `[Jant] GitHub Sync connect failed for ${body.repo}: ${detail}\n`,
1384
+ );
1385
+ return dsToast(`Could not access the repository: ${detail}`, "error");
1386
+ }
1387
+
1388
+ // Check if this repo already has a Jant webhook
1389
+ try {
1390
+ const hooks = await client.listWebhooks(parsed.owner, parsed.repo);
1391
+ const existingJantHook = hooks.find((h) =>
1392
+ h.config.url?.includes("/api/github-sync/webhook"),
1393
+ );
1394
+ if (existingJantHook) {
1395
+ return dsToast(
1396
+ "This repository is already connected to a Jant site. Disconnect it first before connecting to a new site.",
1397
+ "error",
1398
+ );
1399
+ }
1400
+ } catch {
1401
+ // If listing webhooks fails (permissions), skip the check and continue
1402
+ }
1403
+
1404
+ // Save config
1405
+ await c.var.services.settings.set("GITHUB_SYNC_TOKEN", body.token);
1406
+ await c.var.services.settings.set("GITHUB_SYNC_REPO", body.repo);
1407
+ await c.var.services.settings.set("GITHUB_SYNC_AUTH_MODE", "pat");
1408
+ await c.var.services.settings.set("GITHUB_SYNC_APP_INSTALLATION_ID", "");
1409
+ await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
1410
+
1411
+ // Create webhook
1412
+ const { createGitHubSyncService } =
1413
+ await import("../../services/github-sync.js");
1414
+ const syncService = createGitHubSyncService(
1415
+ c.var.services,
1416
+ c.var.currentSite.id,
1417
+ await buildSyncSiteConfig(c),
1418
+ { storage: c.var.storage, githubApp: getGitHubAppConfig(c.env) },
1419
+ );
1420
+ const siteUrl = c.var.appConfig.siteUrl.replace(/\/+$/, "");
1421
+ try {
1422
+ await syncService.setupWebhook(`${siteUrl}/api/github-sync/webhook`);
1423
+ } catch {
1424
+ return dsToast(
1425
+ "Connected, but webhook creation failed. You may need to create it manually.",
1426
+ "error",
1427
+ );
1428
+ }
1429
+
1430
+ // Kick off an initial background push so "Last sync" doesn't sit on
1431
+ // "Not synced yet" until the user's next edit. See the App flow's
1432
+ // equivalent block below for why we bypass the queue.
1433
+ await markSyncPending(c.var.services.settings);
1434
+ const initialSync = runBackgroundSync(c.var.services.settings, syncService);
1435
+ try {
1436
+ c.executionCtx?.waitUntil(initialSync);
1437
+ } catch {
1438
+ // executionCtx unavailable (tests / Node).
1439
+ }
1440
+
1441
+ return dsRedirect(publicPath(c, "/settings/github-sync"));
1442
+ });
1443
+
1444
+ settingsRoutes.post("/github-sync/push", async (c) => {
1445
+ const { createGitHubSyncService } =
1446
+ await import("../../services/github-sync.js");
1447
+ const syncService = createGitHubSyncService(
1448
+ c.var.services,
1449
+ c.var.currentSite.id,
1450
+ await buildSyncSiteConfig(c),
1451
+ { storage: c.var.storage, githubApp: getGitHubAppConfig(c.env) },
1452
+ );
1453
+
1454
+ const config = await syncService.getConfig();
1455
+ if (!config) {
1456
+ return dsToast("GitHub Sync is not configured.", "error");
1457
+ }
1458
+
1459
+ // Run the push in the background so the status card's live "Syncing…"
1460
+ // indicator drives the UX instead of a toast. We patch the status card
1461
+ // into its pending state immediately so Datastar re-evaluates `data-init`
1462
+ // on the new element and opens the SSE status stream, which then polls
1463
+ // until the push completes.
1464
+ await markSyncPending(c.var.services.settings);
1465
+ const push = runBackgroundSync(c.var.services.settings, syncService);
1466
+ try {
1467
+ c.executionCtx?.waitUntil(push);
1468
+ } catch {
1469
+ // executionCtx unavailable (tests / Node).
1470
+ }
1471
+
1472
+ const status = await readGitHubSyncStatus(c); // pending=true after markSyncPending
1473
+ const streamUrl = getSyncStatusStreamUrl(c);
1474
+ const html = renderStatusCardHtml(c, status, streamUrl);
1475
+ return sse(c, async (stream) => {
1476
+ stream.patchElements(html, {
1477
+ mode: "outer",
1478
+ selector: "#github-sync-status",
1479
+ });
1480
+ });
1481
+ });
1482
+
1483
+ settingsRoutes.post("/github-sync/disconnect", async (c) => {
1484
+ const { createGitHubSyncService } =
1485
+ await import("../../services/github-sync.js");
1486
+ const syncService = createGitHubSyncService(
1487
+ c.var.services,
1488
+ c.var.currentSite.id,
1489
+ await buildSyncSiteConfig(c),
1490
+ { githubApp: getGitHubAppConfig(c.env) },
1491
+ );
1492
+ await syncService.teardownWebhook();
1493
+
1494
+ return dsRedirect(publicPath(c, "/settings/github-sync"));
1495
+ });
1496
+
1497
+ // ---------------------------------------------------------------------------
1498
+ // GitHub App install flow
1499
+ // ---------------------------------------------------------------------------
1500
+
1501
+ /**
1502
+ * Redirect the user to GitHub to install the App on their account/org.
1503
+ *
1504
+ * Only available when GitHub App env vars are configured. Uses a signed
1505
+ * state cookie for CSRF protection.
1506
+ */
1507
+ settingsRoutes.get("/github-sync/app/install", async (c) => {
1508
+ const app = getGitHubAppConfig(c.env);
1509
+ if (!app) {
1510
+ return c.text("GitHub App is not configured on this deployment.", 404);
1511
+ }
1512
+
1513
+ // Reinstall-same-account predicate (§5): when this site already has at
1514
+ // least one authorized installation, skip the GitHub round-trip and
1515
+ // render the picker directly. The GitHub redirect is a dead end when
1516
+ // the App is already installed on the chosen account — GitHub shows
1517
+ // its Configure page and never comes back with installation_id.
1518
+ //
1519
+ // `?force=new` bypasses the predicate so the picker's "Install on
1520
+ // another account" action can still reach GitHub to add a fresh
1521
+ // install.
1522
+ const forceNew = c.req.query("force") === "new";
1523
+ if (!forceNew) {
1524
+ const existing =
1525
+ await c.var.services.githubAppInstallations.listInstallationsForSite(
1526
+ c.var.currentSite.id,
1527
+ );
1528
+ if (existing.length > 0) {
1529
+ const navData = await getNavigationData(c);
1530
+ const base = publicPath(c, "/settings/github-sync");
1531
+ const labels = buildRepoPickerLabels(c);
1532
+ const suggestedRepoName = buildSuggestedRepoName(c);
1533
+ return renderPublicPage(c, {
1534
+ title: buildPageTitle(
1535
+ "GitHub Sync — Pick Repository",
1536
+ navData.siteName,
1537
+ ),
1538
+ navData,
1539
+ content: (
1540
+ <>
1541
+ <AdminBreadcrumb
1542
+ parent={breadcrumbLabel(c, "settings")}
1543
+ parentHref={publicPath(c, "/settings")}
1544
+ current={breadcrumbLabel(c, "githubSync")}
1545
+ />
1546
+ <jant-repo-picker
1547
+ labels={labels}
1548
+ api-base={`${base}/app`}
1549
+ connect-url={`${base}/app/connect`}
1550
+ install-url={`${base}/app/install?force=new`}
1551
+ cancel-url={publicPath(c, "/settings")}
1552
+ create-repo-name-hint={suggestedRepoName}
1553
+ >
1554
+ <div class="flex flex-col gap-6 max-w-lg">
1555
+ <div>
1556
+ <h2 class="text-lg font-medium mb-1">Pick a repository</h2>
1557
+ <p class="text-sm text-muted-foreground">
1558
+ Loading repositories…
1559
+ </p>
1560
+ </div>
1561
+ </div>
1562
+ </jant-repo-picker>
1563
+ </>
1564
+ ),
1565
+ });
1566
+ }
1567
+ }
1568
+
1569
+ // Build the state token. When running behind a hosted control plane we
1570
+ // sign host+nonce with the shared SSO secret so the control plane can
1571
+ // verify and route the callback back to the correct site host. In
1572
+ // self-hosted single-site mode the secret is absent and a plain nonce
1573
+ // suffices (the App's Callback URL points directly at this site).
1574
+ const nonce = generateInstallNonce();
1575
+ const ssoSecret = getHostedControlPlaneSsoSecret(c.env);
1576
+ const host = new URL(c.var.appConfig.siteUrl).host;
1577
+ const state = ssoSecret
1578
+ ? await signInstallState(host, nonce, ssoSecret)
1579
+ : nonce;
1580
+
1581
+ // Cookie is pinned to this host; compared byte-for-byte on the callback
1582
+ // to defeat CSRF regardless of whether the state is signed.
1583
+ setCookie(c, "jant_gh_app_state", state, {
1584
+ httpOnly: true,
1585
+ sameSite: "Lax",
1586
+ path: "/",
1587
+ maxAge: 600,
1588
+ });
1589
+
1590
+ return c.redirect(buildInstallUrl(app.slug, state));
1591
+ });
1592
+
1593
+ /**
1594
+ * Landing page after the user installs the GitHub App.
1595
+ *
1596
+ * GitHub redirects here with `installation_id`, `setup_action`, and the
1597
+ * `state` we sent. We verify the state, list the installation's repos, and
1598
+ * render a repo picker that POSTs back to `/github-sync/app/connect`.
1599
+ */
1600
+ settingsRoutes.get("/github-sync/app/callback", async (c) => {
1601
+ const app = getGitHubAppConfig(c.env);
1602
+ if (!app) {
1603
+ return c.text("GitHub App is not configured on this deployment.", 404);
1604
+ }
1605
+
1606
+ const installationId = c.req.query("installation_id");
1607
+ const state = c.req.query("state");
1608
+ if (!installationId) {
1609
+ return c.text("Missing installation_id.", 400);
1610
+ }
1611
+
1612
+ const expected = getCookie(c, "jant_gh_app_state");
1613
+ if (!state || !expected || expected !== state) {
1614
+ return c.text("Invalid or expired state.", 400);
1615
+ }
1616
+
1617
+ // Defense in depth: when an SSO secret is present the state should also
1618
+ // HMAC-verify and its embedded host must match the host serving this
1619
+ // request. This blocks a rogue control plane from redirecting a victim
1620
+ // to the wrong site with a replayed token.
1621
+ const ssoSecret = getHostedControlPlaneSsoSecret(c.env);
1622
+ if (ssoSecret) {
1623
+ const payload = await verifyInstallState(state, ssoSecret);
1624
+ const currentHost = new URL(c.var.appConfig.siteUrl).host;
1625
+ if (!payload || payload.host !== currentHost) {
1626
+ return c.text("State signature invalid for this host.", 400);
1627
+ }
1628
+ }
1629
+
1630
+ // One-shot: clear the cookie so it can't be replayed.
1631
+ setCookie(c, "jant_gh_app_state", "", {
1632
+ httpOnly: true,
1633
+ sameSite: "Lax",
1634
+ path: "/",
1635
+ maxAge: 0,
1636
+ });
1637
+
1638
+ // Fetch the account info for this installation and remember it. The
1639
+ // picker's owner dropdown reads the stored list, so subsequent visits
1640
+ // can switch between accounts without re-running the install flow.
1641
+ // Failures are non-fatal: this callback must still render the picker
1642
+ // so the user isn't locked out right after a successful install.
1643
+ try {
1644
+ const installation = await getInstallation(app, installationId);
1645
+ await c.var.services.githubAppInstallations.upsertInstallation(
1646
+ installationId,
1647
+ c.var.currentSite.id,
1648
+ installation.account,
1649
+ );
1650
+ } catch {
1651
+ // Swallow — the legacy inline picker still works with just the
1652
+ // installation_id from the URL, just without the owner list.
1653
+ }
1654
+
1655
+ const navData = await getNavigationData(c);
1656
+ const base = publicPath(c, "/settings/github-sync");
1657
+ const labels = buildRepoPickerLabels(c);
1658
+ const suggestedRepoName = buildSuggestedRepoName(c);
1659
+
1660
+ return renderPublicPage(c, {
1661
+ title: buildPageTitle("GitHub Sync — Pick Repository", navData.siteName),
1662
+ navData,
1663
+ content: (
1664
+ <>
1665
+ <AdminBreadcrumb
1666
+ parent={breadcrumbLabel(c, "settings")}
1667
+ parentHref={publicPath(c, "/settings")}
1668
+ current={breadcrumbLabel(c, "githubSync")}
1669
+ />
1670
+ <jant-repo-picker
1671
+ labels={labels}
1672
+ api-base={`${base}/app`}
1673
+ connect-url={`${base}/app/connect`}
1674
+ install-url={`${base}/app/install`}
1675
+ cancel-url={publicPath(c, "/settings")}
1676
+ create-repo-name-hint={suggestedRepoName}
1677
+ >
1678
+ {/* SSR fallback while the Lit component upgrades. */}
1679
+ <div class="flex flex-col gap-6 max-w-lg">
1680
+ <div>
1681
+ <h2 class="text-lg font-medium mb-1">Pick a repository</h2>
1682
+ <p class="text-sm text-muted-foreground">Loading repositories…</p>
1683
+ </div>
1684
+ </div>
1685
+ </jant-repo-picker>
1686
+ </>
1687
+ ),
1688
+ });
1689
+ });
1690
+
1691
+ /**
1692
+ * Derive a default repository name to prefill on github.com/new.
1693
+ *
1694
+ * Uses the site's host — the first DNS label is a stable, URL-safe
1695
+ * identifier tied to this specific Jant instance. Fallback to
1696
+ * "jant-site-sync" when the host parse fails so we never hand GitHub an
1697
+ * empty `name=`. The `-jant-sync` suffix disambiguates the sync mirror
1698
+ * from a user's own `{slug}-jant` source repo.
1699
+ */
1700
+ function buildSuggestedRepoName(c: Context<Env>): string {
1701
+ let firstLabel = "";
1702
+ try {
1703
+ firstLabel = new URL(c.var.appConfig.siteUrl).host.split(".")[0] ?? "";
1704
+ } catch {
1705
+ /* fall through */
1706
+ }
1707
+ const slug = firstLabel
1708
+ .toLowerCase()
1709
+ .replace(/[^a-z0-9-_]+/g, "-")
1710
+ .replace(/^-+|-+$/g, "");
1711
+ return slug ? `${slug}-jant-sync` : "jant-site-sync";
1712
+ }
1713
+
1714
+ function buildRepoPickerLabels(c: Context<Env>): string {
1715
+ const i18n = getI18n(c);
1716
+ return JSON.stringify({
1717
+ pageTitle: i18n._(
1718
+ msg({
1719
+ message: "Pick a repository",
1720
+ comment: "@context: GitHub sync picker — page heading",
1721
+ }),
1722
+ ),
1723
+ pageSubtitle: i18n._(
1724
+ msg({
1725
+ message:
1726
+ "Choose the GitHub account and repository to sync with this site.",
1727
+ comment: "@context: GitHub sync picker — page subtitle",
1728
+ }),
1729
+ ),
1730
+ ownerLabel: i18n._(
1731
+ msg({
1732
+ message: "Account",
1733
+ comment: "@context: GitHub sync picker — owner dropdown label",
1734
+ }),
1735
+ ),
1736
+ ownerPlaceholder: i18n._(
1737
+ msg({
1738
+ message: "Select an account",
1739
+ comment:
1740
+ "@context: GitHub sync picker — owner dropdown placeholder when nothing is selected",
1741
+ }),
1742
+ ),
1743
+ ownerEmpty: i18n._(
1744
+ msg({
1745
+ message: "No accounts authorized yet",
1746
+ comment:
1747
+ "@context: GitHub sync picker — shown when the owner list is empty",
1748
+ }),
1749
+ ),
1750
+ installAnother: i18n._(
1751
+ msg({
1752
+ message: "+ Install on another account",
1753
+ comment:
1754
+ "@context: GitHub sync picker — entry at the bottom of the owner dropdown that starts a new install flow",
1755
+ }),
1756
+ ),
1757
+ repositoryLabel: i18n._(
1758
+ msg({
1759
+ message: "Repository",
1760
+ comment: "@context: GitHub sync picker — repo dropdown label",
1761
+ }),
1762
+ ),
1763
+ repoPlaceholderNoOwner: i18n._(
1764
+ msg({
1765
+ message: "Pick an account first",
1766
+ comment:
1767
+ "@context: GitHub sync picker — repo dropdown placeholder before an owner is chosen",
1768
+ }),
1769
+ ),
1770
+ repoPlaceholder: i18n._(
1771
+ msg({
1772
+ message: "Select a repository",
1773
+ comment: "@context: GitHub sync picker — repo dropdown placeholder",
1774
+ }),
1775
+ ),
1776
+ repoSearchPlaceholder: i18n._(
1777
+ msg({
1778
+ message: "Search repositories",
1779
+ comment: "@context: GitHub sync picker — search input placeholder",
1780
+ }),
1781
+ ),
1782
+ repoEmpty: i18n._(
1783
+ msg({
1784
+ message: "No repositories match.",
1785
+ comment:
1786
+ "@context: GitHub sync picker — empty state when search returns nothing",
1787
+ }),
1788
+ ),
1789
+ repoLoading: i18n._(
1790
+ msg({
1791
+ message: "Loading…",
1792
+ comment: "@context: GitHub sync picker — loading state",
1793
+ }),
1794
+ ),
1795
+ repoShowingOf: i18n._(
1796
+ msg({
1797
+ message: "Showing {shown} of {total}",
1798
+ comment:
1799
+ "@context: GitHub sync picker — paginated repo count hint. Placeholders {shown} and {total} are integers.",
1800
+ }),
1801
+ ),
1802
+ repoSearchHint: i18n._(
1803
+ msg({
1804
+ message: "type to search all",
1805
+ comment:
1806
+ "@context: GitHub sync picker — hint telling the user typing will search the full list",
1807
+ }),
1808
+ ),
1809
+ refreshRepos: i18n._(
1810
+ msg({
1811
+ message: "Refresh repository list",
1812
+ comment:
1813
+ "@context: GitHub sync picker — tooltip for the refresh button next to the search input",
1814
+ }),
1815
+ ),
1816
+ createOnGitHub: i18n._(
1817
+ msg({
1818
+ message: "Create a new repository on GitHub",
1819
+ comment:
1820
+ "@context: GitHub sync picker — entry that opens github.com/new in a new tab",
1821
+ }),
1822
+ ),
1823
+ createOnGitHubHint: i18n._(
1824
+ msg({
1825
+ message: "We'll prefill the name {name}. The list refreshes on return.",
1826
+ comment:
1827
+ "@context: GitHub sync picker — hint under the create-on-github entry. Placeholder {name} is the suggested repo name.",
1828
+ }),
1829
+ ),
1830
+ classifyLoading: i18n._(
1831
+ msg({
1832
+ message: "Checking repository…",
1833
+ comment:
1834
+ "@context: GitHub sync picker — status text while classifying the picked repo",
1835
+ }),
1836
+ ),
1837
+ classificationEmpty: i18n._(
1838
+ msg({
1839
+ message: "Empty repository. Ready to connect.",
1840
+ comment:
1841
+ "@context: GitHub sync picker — shown when the selected repo has no commits",
1842
+ }),
1843
+ ),
1844
+ classificationOwned: i18n._(
1845
+ msg({
1846
+ message: "This repository is already backing up this site.",
1847
+ comment:
1848
+ "@context: GitHub sync picker — shown when the selected repo's marker matches this site",
1849
+ }),
1850
+ ),
1851
+ classificationOwnedByOther: i18n._(
1852
+ msg({
1853
+ message:
1854
+ "This repository is already backing up another Jant site ({host}). Pick a different repository.",
1855
+ comment:
1856
+ "@context: GitHub sync picker — blocking message when marker belongs to another site. Placeholder {host} is the other site's host.",
1857
+ }),
1858
+ ),
1859
+ classificationForeign: i18n._(
1860
+ msg({
1861
+ message: "This repository has existing content.",
1862
+ comment:
1863
+ "@context: GitHub sync picker — heading above the foreign-repo confirmation",
1864
+ }),
1865
+ ),
1866
+ confirmHeading: i18n._(
1867
+ msg({
1868
+ message: "This repository already has commits",
1869
+ comment:
1870
+ "@context: GitHub sync picker — confirmation card heading for foreign repos",
1871
+ }),
1872
+ ),
1873
+ confirmBody: i18n._(
1874
+ msg({
1875
+ message:
1876
+ "Connecting will sync your site onto {repo}'s default branch on top of its existing history. Existing files outside Jant's managed paths are kept. This can't be undone.",
1877
+ comment:
1878
+ "@context: GitHub sync picker — explanatory body for foreign-repo confirmation. Placeholder {repo} is the owner/repo slug.",
1879
+ }),
1880
+ ),
1881
+ confirmInputLabel: i18n._(
1882
+ msg({
1883
+ message: "Type {repo} to confirm",
1884
+ comment:
1885
+ "@context: GitHub sync picker — label above the typed-confirmation input",
1886
+ }),
1887
+ ),
1888
+ confirmInputPlaceholder: i18n._(
1889
+ msg({
1890
+ message: "owner/repo",
1891
+ comment:
1892
+ "@context: GitHub sync picker — placeholder text showing the expected input shape",
1893
+ }),
1894
+ ),
1895
+ cancel: i18n._(
1896
+ msg({
1897
+ message: "Cancel",
1898
+ comment: "@context: GitHub sync picker — cancel action",
1899
+ }),
1900
+ ),
1901
+ connect: i18n._(
1902
+ msg({
1903
+ message: "Connect",
1904
+ comment: "@context: GitHub sync picker — primary action button",
1905
+ }),
1906
+ ),
1907
+ connecting: i18n._(
1908
+ msg({
1909
+ message: "Connecting…",
1910
+ comment:
1911
+ "@context: GitHub sync picker — primary action button while a request is in flight",
1912
+ }),
1913
+ ),
1914
+ privateBadge: i18n._(
1915
+ msg({
1916
+ message: "Private",
1917
+ comment: "@context: GitHub sync picker — badge next to private repos",
1918
+ }),
1919
+ ),
1920
+ connectionFailed: i18n._(
1921
+ msg({
1922
+ message: "Couldn't connect. Check the error and try again.",
1923
+ comment:
1924
+ "@context: GitHub sync picker — generic failure message for Connect",
1925
+ }),
1926
+ ),
1927
+ retry: i18n._(
1928
+ msg({
1929
+ message: "Try again",
1930
+ comment: "@context: GitHub sync picker — retry button",
1931
+ }),
1932
+ ),
1933
+ }).replace(/</g, "\\u003c");
1934
+ }
1935
+
1936
+ /**
1937
+ * Finalize the App connection: validate access, classify the target repo
1938
+ * to gate foreign/conflicting choices behind explicit user confirmation,
1939
+ * then persist the installation id and chosen repo and register the webhook.
1940
+ *
1941
+ * Accepts both classic form submits (redirects on success) and JSON
1942
+ * requests (returns JSON). Phase 3's picker component uses the JSON path.
1943
+ */
1944
+ settingsRoutes.post("/github-sync/app/connect", async (c) => {
1945
+ const app = getGitHubAppConfig(c.env);
1946
+ if (!app) {
1947
+ return c.text("GitHub App is not configured on this deployment.", 404);
1948
+ }
1949
+
1950
+ const wantsJson = c.req.header("content-type")?.includes("application/json");
1951
+ const body = wantsJson
1952
+ ? ((await c.req.json().catch(() => ({}))) as Record<string, unknown>)
1953
+ : await c.req.parseBody();
1954
+
1955
+ const installationId = String(body.installationId ?? "").trim();
1956
+ const repo = String(body.repo ?? "").trim();
1957
+ const confirmForeign =
1958
+ body.confirmForeign === true || body.confirmForeign === "true";
1959
+ if (!installationId || !repo) {
1960
+ return wantsJson
1961
+ ? c.json({ error: "Missing installationId or repo." }, 400)
1962
+ : c.text("Missing installationId or repo.", 400);
1963
+ }
1964
+
1965
+ const { parseRepoSlug, createGitHubClient } =
1966
+ await import("../../lib/github-api.js");
1967
+ const parsed = parseRepoSlug(repo);
1968
+ if (!parsed) {
1969
+ return wantsJson
1970
+ ? c.json({ error: "Invalid repository format." }, 400)
1971
+ : c.text("Invalid repository format.", 400);
1972
+ }
1973
+
1974
+ // Classify the repo before we commit any state: empty and already-owned
1975
+ // targets proceed immediately; foreign or other-site targets require an
1976
+ // explicit `confirmForeign` flag from the caller (a typed confirmation
1977
+ // in the UI) to avoid silently pushing onto unrelated histories.
1978
+ const { classifyRepoForSync } = await import("../../services/github-sync.js");
1979
+ const ghClient = createGitHubClient(() =>
1980
+ getInstallationTokenFromApp(app, installationId),
1981
+ );
1982
+ let classification;
1983
+ try {
1984
+ classification = await classifyRepoForSync(
1985
+ ghClient,
1986
+ parsed.owner,
1987
+ parsed.repo,
1988
+ c.var.currentSite.id,
1989
+ );
1990
+ } catch (err) {
1991
+ const detail = err instanceof Error ? err.message : String(err);
1992
+ return wantsJson
1993
+ ? c.json({ error: `Could not read repository: ${detail}` }, 500)
1994
+ : c.text(`Could not read repository: ${detail}`, 500);
1995
+ }
1996
+
1997
+ if (classification.kind === "owned-by-other-site") {
1998
+ const msg = `This repository is already backing up another Jant site (${classification.marker.site_host}). Pick a different repository.`;
1999
+ return wantsJson
2000
+ ? c.json(
2001
+ {
2002
+ error: msg,
2003
+ classification: "owned-by-other-site",
2004
+ marker: classification.marker,
2005
+ },
2006
+ 409,
2007
+ )
2008
+ : c.text(msg, 409);
2009
+ }
2010
+
2011
+ if (classification.kind === "foreign" && !confirmForeign) {
2012
+ const msg = `${repo} already has commits but isn't managed by Jant. Re-submit with confirmation to push on top of its history.`;
2013
+ return wantsJson
2014
+ ? c.json(
2015
+ {
2016
+ error: msg,
2017
+ classification: "foreign",
2018
+ defaultBranch: classification.defaultBranch,
2019
+ },
2020
+ 409,
2021
+ )
2022
+ : c.text(msg, 409);
2023
+ }
2024
+
2025
+ // Persist config before creating webhook so the sync service can load it.
2026
+ await c.var.services.settings.set("GITHUB_SYNC_AUTH_MODE", "app");
2027
+ await c.var.services.settings.set(
2028
+ "GITHUB_SYNC_APP_INSTALLATION_ID",
2029
+ installationId,
2030
+ );
2031
+ await c.var.services.settings.set("GITHUB_SYNC_REPO", repo);
2032
+ await c.var.services.settings.set("GITHUB_SYNC_TOKEN", "");
2033
+ await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
2034
+
2035
+ const { createGitHubSyncService } =
2036
+ await import("../../services/github-sync.js");
2037
+ const syncService = createGitHubSyncService(
2038
+ c.var.services,
2039
+ c.var.currentSite.id,
2040
+ await buildSyncSiteConfig(c),
2041
+ { storage: c.var.storage, githubApp: app },
2042
+ );
2043
+ const siteUrl = c.var.appConfig.siteUrl.replace(/\/+$/, "");
2044
+ try {
2045
+ await syncService.setupWebhook(`${siteUrl}/api/github-sync/webhook`);
2046
+ } catch (err) {
2047
+ const detail = err instanceof Error ? err.message : String(err);
2048
+ const msg = `Connected, but webhook creation failed: ${detail}. You may need to create it manually.`;
2049
+ return wantsJson ? c.json({ error: msg }, 500) : c.text(msg, 500);
2050
+ }
2051
+
2052
+ // Kick off an initial push so the user's content lands in the repo
2053
+ // immediately. We can't use the queue path: CF Queue isn't wired up
2054
+ // for most self-hosted deployments, and the noop fallback would
2055
+ // silently drop the job. Instead, set PENDING and run the sync in
2056
+ // the background via waitUntil. The status page reads PENDING and
2057
+ // polls until it clears, so the user sees "Syncing…" right away.
2058
+ await markSyncPending(c.var.services.settings);
2059
+ const initialSync = runBackgroundSync(c.var.services.settings, syncService);
2060
+ try {
2061
+ c.executionCtx?.waitUntil(initialSync);
2062
+ } catch {
2063
+ // executionCtx unavailable (tests / Node) — let the promise resolve
2064
+ // on its own; response still returns immediately.
2065
+ }
2066
+
2067
+ const redirect = publicPath(c, "/settings/github-sync");
2068
+ return wantsJson ? c.json({ ok: true, redirect }) : c.redirect(redirect);
2069
+ });
2070
+
2071
+ // ---------------------------------------------------------------------------
2072
+ // GitHub App picker JSON API (consumed by the jant-repo-picker component)
2073
+ // ---------------------------------------------------------------------------
2074
+
2075
+ /**
2076
+ * Helper: load the GitHub App config or short-circuit with a 404 JSON error.
2077
+ */
2078
+ type RequireAppResult =
2079
+ | { app: import("../../lib/env.js").GitHubAppEnvConfig; response: null }
2080
+ | { app: null; response: Response };
2081
+
2082
+ function requireGitHubApp(c: Context<Env>): RequireAppResult {
2083
+ const app = getGitHubAppConfig(c.env);
2084
+ if (!app) {
2085
+ return {
2086
+ app: null,
2087
+ response: c.json(
2088
+ { error: "GitHub App is not configured on this deployment." },
2089
+ 404,
2090
+ ),
2091
+ };
2092
+ }
2093
+ return { app, response: null };
2094
+ }
2095
+
2096
+ /**
2097
+ * Dynamic import shim for `getInstallationToken`. Used by `/connect` to
2098
+ * build a client for classification without adding the helper to the
2099
+ * top-level imports (the module already lazy-imports github-api).
2100
+ */
2101
+ async function getInstallationTokenFromApp(
2102
+ app: import("../../lib/env.js").GitHubAppEnvConfig,
2103
+ installationId: string,
2104
+ ): Promise<string> {
2105
+ const { getInstallationToken } = await import("../../lib/github-app.js");
2106
+ return getInstallationToken(app, installationId);
2107
+ }
2108
+
2109
+ /** List GitHub App installations authorized for this site. */
2110
+ settingsRoutes.get("/github-sync/app/installations", async (c) => {
2111
+ const installations =
2112
+ await c.var.services.githubAppInstallations.listInstallationsForSite(
2113
+ c.var.currentSite.id,
2114
+ );
2115
+ return c.json({
2116
+ installations: installations.map((entry) => ({
2117
+ installationId: entry.installationId,
2118
+ account: entry.account,
2119
+ addedAt: entry.addedAt,
2120
+ })),
2121
+ });
2122
+ });
2123
+
2124
+ /**
2125
+ * List (or search) repositories accessible via an installation.
2126
+ *
2127
+ * Query params:
2128
+ * - installationId (required)
2129
+ * - page (default 1) — only used when `q` is empty
2130
+ * - q (optional) — when non-empty switches to GitHub search API
2131
+ *
2132
+ * 401 from GitHub indicates the installation was removed; we lazy-clean
2133
+ * the stored entry and return a 410 so the UI can refresh its list.
2134
+ */
2135
+ settingsRoutes.get("/github-sync/app/repos", async (c) => {
2136
+ const { app, response } = requireGitHubApp(c);
2137
+ if (!app) return response;
2138
+
2139
+ const installationId = c.req.query("installationId")?.trim();
2140
+ if (!installationId) {
2141
+ return c.json({ error: "Missing installationId." }, 400);
2142
+ }
2143
+ const q = c.req.query("q")?.trim() ?? "";
2144
+ const pageParam = Number(c.req.query("page") ?? "1");
2145
+ const page =
2146
+ Number.isFinite(pageParam) && pageParam > 0 ? Math.floor(pageParam) : 1;
2147
+
2148
+ try {
2149
+ if (q) {
2150
+ const installations =
2151
+ await c.var.services.githubAppInstallations.listInstallationsForSite(
2152
+ c.var.currentSite.id,
2153
+ );
2154
+ const installation = installations.find(
2155
+ (i) => i.installationId === installationId,
2156
+ );
2157
+ if (!installation) {
2158
+ return c.json({ error: "Unknown installation." }, 404);
2159
+ }
2160
+ const result = await searchInstallationRepos(
2161
+ app,
2162
+ installationId,
2163
+ installation.account.login,
2164
+ q,
2165
+ );
2166
+ return c.json({
2167
+ repos: result.repos,
2168
+ totalCount: result.totalCount,
2169
+ hasMore: false, // search API doesn't page for us here
2170
+ nextPage: null,
2171
+ mode: "search",
2172
+ });
2173
+ }
2174
+
2175
+ const result = await listInstallationReposPage(app, installationId, page);
2176
+ return c.json({
2177
+ repos: result.repos,
2178
+ totalCount: result.totalCount,
2179
+ hasMore: result.hasMore,
2180
+ nextPage: result.nextPage,
2181
+ mode: "list",
2182
+ });
2183
+ } catch (err) {
2184
+ const detail = err instanceof Error ? err.message : String(err);
2185
+ // GitHub returns 401 when an installation was uninstalled on their
2186
+ // side. Clean up our cached entry so the UI stops showing a dead owner.
2187
+ if (/\b401\b/.test(detail) || /\b404\b/.test(detail)) {
2188
+ await c.var.services.githubAppInstallations.removeInstallation(
2189
+ installationId,
2190
+ c.var.currentSite.id,
2191
+ );
2192
+ return c.json(
2193
+ { error: "Installation is no longer accessible.", removed: true },
2194
+ 410,
2195
+ );
2196
+ }
2197
+ return c.json({ error: detail }, 500);
2198
+ }
2199
+ });
2200
+
2201
+ /**
2202
+ * Classify a repo: empty / owned / owned-by-other-site / foreign.
2203
+ *
2204
+ * The picker calls this when the user picks a repo so it can show an
2205
+ * appropriate confirmation UI before submitting `/connect`.
2206
+ */
2207
+ settingsRoutes.post("/github-sync/app/classify", async (c) => {
2208
+ const { app, response } = requireGitHubApp(c);
2209
+ if (!app) return response;
2210
+
2211
+ const body = (await c.req.json().catch(() => ({}))) as Record<
2212
+ string,
2213
+ unknown
2214
+ >;
2215
+ const installationId = String(body.installationId ?? "").trim();
2216
+ const repo = String(body.repo ?? "").trim();
2217
+ if (!installationId || !repo) {
2218
+ return c.json({ error: "Missing installationId or repo." }, 400);
2219
+ }
2220
+
2221
+ const { parseRepoSlug, createGitHubClient } =
2222
+ await import("../../lib/github-api.js");
2223
+ const parsed = parseRepoSlug(repo);
2224
+ if (!parsed) {
2225
+ return c.json({ error: "Invalid repository format." }, 400);
2226
+ }
2227
+
2228
+ const { classifyRepoForSync } = await import("../../services/github-sync.js");
2229
+ const client = createGitHubClient(() =>
2230
+ getInstallationTokenFromApp(app, installationId),
2231
+ );
2232
+ try {
2233
+ const classification = await classifyRepoForSync(
2234
+ client,
2235
+ parsed.owner,
2236
+ parsed.repo,
2237
+ c.var.currentSite.id,
2238
+ );
2239
+ return c.json({ classification });
2240
+ } catch (err) {
2241
+ const detail = err instanceof Error ? err.message : String(err);
2242
+ return c.json({ error: detail }, 500);
2243
+ }
2244
+ });
2245
+
2246
+ settingsRoutes.get("/github-sync", async (c) => {
2247
+ const [
2248
+ enabled,
2249
+ repo,
2250
+ lastPushSha,
2251
+ webhookId,
2252
+ lastPushAt,
2253
+ authMode,
2254
+ lastError,
2255
+ ] = await Promise.all([
2256
+ c.var.services.settings.get("GITHUB_SYNC_ENABLED"),
2257
+ c.var.services.settings.get("GITHUB_SYNC_REPO"),
2258
+ c.var.services.settings.get("GITHUB_SYNC_LAST_PUSH_SHA"),
2259
+ c.var.services.settings.get("GITHUB_SYNC_WEBHOOK_ID"),
2260
+ c.var.services.settings.get("GITHUB_SYNC_LAST_PUSH_AT"),
2261
+ c.var.services.settings.get("GITHUB_SYNC_AUTH_MODE"),
2262
+ c.var.services.settings.get("GITHUB_SYNC_LAST_ERROR"),
2263
+ ]);
2264
+ // Use isSyncPending (not raw flag) so a worker that died mid-push
2265
+ // doesn't leave the UI stuck on "Syncing…" forever.
2266
+ const pending = await isSyncPending(c.var.services.settings);
2267
+
2268
+ const status: GitHubSyncStatus = {
2269
+ enabled: enabled === "true",
2270
+ repo: repo ?? null,
2271
+ lastPushSha: lastPushSha ?? null,
2272
+ webhookId: webhookId ?? null,
2273
+ lastPushAt: lastPushAt ? Number(lastPushAt) : null,
2274
+ authMode: authMode === "app" ? "app" : "pat",
2275
+ appConfigured: getGitHubAppConfig(c.env) !== null,
2276
+ pending,
2277
+ lastError: lastError || null,
2278
+ };
2279
+
2280
+ const navData = await getNavigationData(c);
2281
+
2282
+ return renderPublicPage(c, {
2283
+ title: buildPageTitle("GitHub Sync", navData.siteName),
2284
+ navData,
2285
+ content: (
2286
+ <>
2287
+ <AdminBreadcrumb
2288
+ parent={breadcrumbLabel(c, "settings")}
2289
+ parentHref={publicPath(c, "/settings")}
2290
+ current={breadcrumbLabel(c, "githubSync")}
2291
+ />
2292
+ <GitHubSyncContent
2293
+ status={status}
2294
+ sitePathPrefix={c.var.appConfig.sitePathPrefix}
2295
+ />
2296
+ </>
2297
+ ),
2298
+ });
2299
+ });