@jant/core 0.3.38 → 0.3.40

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 (1181) hide show
  1. package/LICENSE +189 -189
  2. package/README.md +6 -4
  3. package/bin/commands/assets/prepare.js +55 -0
  4. package/bin/commands/db/execute-file.js +113 -0
  5. package/bin/commands/db/export.js +5 -0
  6. package/bin/commands/db/rehearse.js +65 -0
  7. package/bin/commands/deploy.js +162 -0
  8. package/bin/commands/export.js +73 -75
  9. package/bin/commands/import-site.js +1531 -267
  10. package/bin/commands/migrate.js +79 -0
  11. package/bin/commands/reset-password.js +80 -7
  12. package/bin/commands/site/export.js +415 -0
  13. package/bin/commands/site/import.js +5 -0
  14. package/bin/commands/site/localize-media.js +118 -0
  15. package/bin/commands/site/snapshot/export.js +357 -0
  16. package/bin/commands/site/snapshot/import.js +363 -0
  17. package/bin/commands/start.js +46 -0
  18. package/bin/commands/uploads/cleanup.js +119 -0
  19. package/bin/jant.js +55 -15
  20. package/bin/lib/cli-api-token.js +6 -0
  21. package/bin/lib/d1-query.js +194 -0
  22. package/bin/lib/load-node-runtime.js +10 -0
  23. package/bin/lib/migration-artifacts.js +99 -0
  24. package/bin/lib/migration-rehearsal.js +438 -0
  25. package/bin/lib/migration-runner.js +311 -0
  26. package/bin/lib/node-database.js +48 -0
  27. package/bin/lib/node-sqlite.js +158 -0
  28. package/bin/lib/public-assets.js +66 -0
  29. package/bin/lib/r2-query.js +170 -0
  30. package/bin/lib/runtime-target.js +33 -0
  31. package/bin/lib/site-localize-media.js +427 -0
  32. package/bin/lib/site-media-parser.js +1150 -0
  33. package/bin/lib/site-selection.js +222 -0
  34. package/bin/lib/site-snapshot.js +484 -0
  35. package/bin/lib/site-url.js +84 -0
  36. package/bin/lib/sql-export.js +194 -0
  37. package/bin/lib/wrangler-cli.js +41 -0
  38. package/bin/lib/wrangler-config.js +86 -0
  39. package/dist/app-CAtsuLLh.js +30644 -0
  40. package/dist/client/_assets/chunks/0041f681602cc834bb5c55ced0155b8e-BNpHLJgt.woff2 +0 -0
  41. package/dist/client/_assets/chunks/008ea9091e332c639ceb18874eacd60c-CygB704Q.woff2 +0 -0
  42. package/dist/client/_assets/chunks/00c9ac960d866ffaf8a866e5939024e2-CqRrlxSi.woff2 +0 -0
  43. package/dist/client/_assets/chunks/02629e5d0a9860b7fe32ec1f0563213a-YvgSIv_V.woff2 +0 -0
  44. package/dist/client/_assets/chunks/02e48e353415a00e0f556608cab33a43-YFgu4zi3.woff2 +0 -0
  45. package/dist/client/_assets/chunks/02faf6bb0ab4d56ada037c0bbaf9b9f7-DUuUM2eo.woff2 +0 -0
  46. package/dist/client/_assets/chunks/031089da45fbfb7dc18ac827bef4c56e-D4ahCOTO.woff2 +0 -0
  47. package/dist/client/_assets/chunks/03ac785139320b7b13bac9c150bf72bf-BG7zubxs.woff2 +0 -0
  48. package/dist/client/_assets/chunks/053bd3d7aec0040d0cc50c261a1f4e3e-BdG4yI0n.woff2 +0 -0
  49. package/dist/client/_assets/chunks/057a3d44d7fc606f113d863376d0ecf0-BsEMbuKV.woff2 +0 -0
  50. package/dist/client/_assets/chunks/0713613227cc4c686c45a279f8bdc166-BzLoJcLo.woff2 +0 -0
  51. package/dist/client/_assets/chunks/0b1f83a3c7e715560a55ad9eb0fb1c94-asGrAhxW.woff2 +0 -0
  52. package/dist/client/_assets/chunks/0c055db157e7a13f3103cc2a6b67fec3-1T6huY_I.woff2 +0 -0
  53. package/dist/client/_assets/chunks/0d3f5cc265cb6c439c517f2c4cebbddf-CmkfpVHX.woff2 +0 -0
  54. package/dist/client/_assets/chunks/0e97f44ebc65384c346fe19bcc52fa20-BJTzmZzt.woff2 +0 -0
  55. package/dist/client/_assets/chunks/10f2b44b3711d3f5bdcc30d373b543d1-BRTxD_6-.woff2 +0 -0
  56. package/dist/client/_assets/chunks/1139d32ae2bdeb26c0c8f31330aa9a9f-B7k4Da0E.woff2 +0 -0
  57. package/dist/client/_assets/chunks/1259e5825b314fe2b8bb96d6e8069ee5-Ba8VoZCg.woff2 +0 -0
  58. package/dist/client/_assets/chunks/12c518ebfe62818af550c08947e359e7-B7eDp07N.woff2 +0 -0
  59. package/dist/client/_assets/chunks/145831a59caa06d894022fe60212ed21-B3xSIk4b.woff2 +0 -0
  60. package/dist/client/_assets/chunks/14b040a2dda256936bc7b3470e548394-CF7zgNWs.woff2 +0 -0
  61. package/dist/client/_assets/chunks/14c1506106d92621bafb11016735194e-cbk_paoP.woff2 +0 -0
  62. package/dist/client/_assets/chunks/154a2c266902003bd8b7449386b10776-CYgd0apt.woff2 +0 -0
  63. package/dist/client/_assets/chunks/15f8c0df47fd639d1b0d9bd5cf507c9b-B11bYwtn.woff2 +0 -0
  64. package/dist/client/_assets/chunks/1668bd859ffe15bed7d5563117d8d5fb-DJotYU-j.woff2 +0 -0
  65. package/dist/client/_assets/chunks/169a096e61d38a773216f51d1ec2cc06-CzS4CZjy.woff2 +0 -0
  66. package/dist/client/_assets/chunks/1884a2b22d314c7d57707f03aec348e0-BdBJ4_JO.woff2 +0 -0
  67. package/dist/client/_assets/chunks/189f272ea2600c74d576b7b15c014922-CylwUmhO.woff2 +0 -0
  68. package/dist/client/_assets/chunks/190e3f8632494e7c095117f26b1c811e-DT09KhZe.woff2 +0 -0
  69. package/dist/client/_assets/chunks/19ad151c22ce1befe0a9ea643fbee570-BOjbPzxL.woff2 +0 -0
  70. package/dist/client/_assets/chunks/19e39850472250bfdbce654d30859879-Db8MVQ-5.woff2 +0 -0
  71. package/dist/client/_assets/chunks/1b5d0d740450fb996749464c9b882025-BG0yepI2.woff2 +0 -0
  72. package/dist/client/_assets/chunks/1bca0a2a8840ad0ee9414940593db144-CfiRZ0Fs.woff2 +0 -0
  73. package/dist/client/_assets/chunks/1c820b5295868008ca7c78afa5b7655d-rGkaVY0Z.woff2 +0 -0
  74. package/dist/client/_assets/chunks/1cda27dcaab977ae4ef5d5ab2a10ae03-3n1CbajX.woff2 +0 -0
  75. package/dist/client/_assets/chunks/1e2640116bbba817f43c43cc69371cf1-BLnekPNy.woff2 +0 -0
  76. package/dist/client/_assets/chunks/1f4bc38a1c50f55f335f5411cae47696-3J1JFXeu.woff2 +0 -0
  77. package/dist/client/_assets/chunks/1fbccc182322b513f57cd156a9a491b0-bRVH28a5.woff2 +0 -0
  78. package/dist/client/_assets/chunks/1fbe225742c69f4ba9ea5f74922f0ca1-BHRIiKDB.woff2 +0 -0
  79. package/dist/client/_assets/chunks/2022cf097cb952d9fe75b53b4587d2c3-Btdq5ZEB.woff2 +0 -0
  80. package/dist/client/_assets/chunks/21e0a71d86be7b8a9e812e7af09dd061-SSZaQU5Z.woff2 +0 -0
  81. package/dist/client/_assets/chunks/2240a3c43ca5ef59ae3c348c7884792f-DQgT8QLe.woff2 +0 -0
  82. package/dist/client/_assets/chunks/2573703213da30d3ba18925b100b2c2b-D61uHyrJ.woff2 +0 -0
  83. package/dist/client/_assets/chunks/265e048cc9f2f8a711ba585a534d5351-geHDzSNf.woff2 +0 -0
  84. package/dist/client/_assets/chunks/26839c0e47c73514b8d8f660d24d6b19-D2gmzZl-.woff2 +0 -0
  85. package/dist/client/_assets/chunks/280e3d2b58e9ad3501816072e01b0c13-Dx5yDwjz.woff2 +0 -0
  86. package/dist/client/_assets/chunks/2874d07e228da9583b0e73646dacd498-26mUURU-.woff2 +0 -0
  87. package/dist/client/_assets/chunks/29d49891713a2785a3a383001cf58c59-DVb6JpZh.woff2 +0 -0
  88. package/dist/client/_assets/chunks/2a22e14a9ad53f2abb3c7e85017b7d12-CU_4APNf.woff2 +0 -0
  89. package/dist/client/_assets/chunks/2a7cedfcd6e4c7cec36f4fd7b0f329c2-BUp7BYdq.woff2 +0 -0
  90. package/dist/client/_assets/chunks/2acea04a920f6af31e7db97052f563c6-KdL2n7-Y.woff2 +0 -0
  91. package/dist/client/_assets/chunks/2ae2ca951489c9d50cde5b36a2a5515b-CWV12jwD.woff2 +0 -0
  92. package/dist/client/_assets/chunks/2b3e8c5703b91f39f6027f43f0da6f4b-FHYDvFqX.woff2 +0 -0
  93. package/dist/client/_assets/chunks/2ba802b14f21a58fc61606c88fa93373-BmrW6szo.woff2 +0 -0
  94. package/dist/client/_assets/chunks/2d81eb6ab0ebbc0cabfb3a3341ba8800-Ds2YcMj0.woff2 +0 -0
  95. package/dist/client/_assets/chunks/2deb444546774c3a3ab38c75eb69cdfb-Dm9USQXk.woff2 +0 -0
  96. package/dist/client/_assets/chunks/2e98b666924b8e0a09d1aeeefd24bdd2-Cmh3DrKG.woff2 +0 -0
  97. package/dist/client/_assets/chunks/2f27ee4fb2cf6a280e110e09c18ef73e-D_yYWYMZ.woff2 +0 -0
  98. package/dist/client/_assets/chunks/2fbccf9a3853eb59db1a825e044515fd-CiShE3Wo.woff2 +0 -0
  99. package/dist/client/_assets/chunks/2fd3fceb6faed5e3db768e88d7614dca-D74stxc-.woff2 +0 -0
  100. package/dist/client/_assets/chunks/2ff009fa8701505d7f3dc6c83763f019-nkOuUfA-.woff2 +0 -0
  101. package/dist/client/_assets/chunks/31342cebfa5ea7fac06b4ea372d96bc5-DdIH_TkU.woff2 +0 -0
  102. package/dist/client/_assets/chunks/31424fe5d54692e7c8b38021ccb8597c-CfNu45zJ.woff2 +0 -0
  103. package/dist/client/_assets/chunks/3179006d1c7ebfa50d27482a2859d9a0-e7PNUH9y.woff2 +0 -0
  104. package/dist/client/_assets/chunks/34c2edb3c37f71258f5c4a31091f0c6c-Cyr8VjPf.woff2 +0 -0
  105. package/dist/client/_assets/chunks/35cf5dd04315e0b906e1a413d7905a2f-A-QPUrVX.woff2 +0 -0
  106. package/dist/client/_assets/chunks/360c190344c26278bbc50e2f4d6a2b3f-B7OhzvgR.woff2 +0 -0
  107. package/dist/client/_assets/chunks/375329ba0b50b94b35006498e555867c-CDc5XB7_.woff2 +0 -0
  108. package/dist/client/_assets/chunks/387c811226f303af62f1e21aae6f5c83-BmPYUAdb.woff2 +0 -0
  109. package/dist/client/_assets/chunks/389a950f2a1211946d294716e679e381-Diuxk8JN.woff2 +0 -0
  110. package/dist/client/_assets/chunks/39b0bbc910af9d2d6dcd8bd4abd6387d-NhvZf71d.woff2 +0 -0
  111. package/dist/client/_assets/chunks/3adc8f6350cf5067bcb6dc5e44c45d41-DIO3oy0b.woff2 +0 -0
  112. package/dist/client/_assets/chunks/3b41385fc27419c19822060daa0b5cb3-Dh5JVnsu.woff2 +0 -0
  113. package/dist/client/_assets/chunks/3bed5bd57de8f738e53cddaea88983d9-BAGVbLtM.woff2 +0 -0
  114. package/dist/client/_assets/chunks/3c201fd8d1bb20abe7d06b940e83a4d9-Bz0dCLvM.woff2 +0 -0
  115. package/dist/client/_assets/chunks/3cbe4a697fd595ef42c899de7d3e5445-zWwFRRI_.woff2 +0 -0
  116. package/dist/client/_assets/chunks/3d385ea0880df7204258e290648ec012-BQqoFH8R.woff2 +0 -0
  117. package/dist/client/_assets/chunks/3d83dacbbec3d8532ae9afede21f3aab-BH06SEJj.woff2 +0 -0
  118. package/dist/client/_assets/chunks/3dc1a4f0d7af59e16b5162a2b077a442-CkXZW3CY.woff2 +0 -0
  119. package/dist/client/_assets/chunks/3dc68e473fe23bd076dd46785cd23583-Cv6HZ0Dr.woff2 +0 -0
  120. package/dist/client/_assets/chunks/427577dcb707d1d35eebd155b4222aa7-D7-mHA6o.woff2 +0 -0
  121. package/dist/client/_assets/chunks/42a74b6a625bbf0a9616ed4db3152c88-CQ58KTMS.woff2 +0 -0
  122. package/dist/client/_assets/chunks/435b7dca567809813fcb395a27ed83a0-C6E3YXBr.woff2 +0 -0
  123. package/dist/client/_assets/chunks/43693195e775d515689fa035394067fd-DLMrklI6.woff2 +0 -0
  124. package/dist/client/_assets/chunks/43fb49e5b79ee7e553869d84e6e08b1e-BgOVrd59.woff2 +0 -0
  125. package/dist/client/_assets/chunks/44dcbbc3cc8f22e613b342d691511ab6-dRyYwGGi.woff2 +0 -0
  126. package/dist/client/_assets/chunks/450a5b53be0a8a778bb0b623e86b652f-qwII2gXW.woff2 +0 -0
  127. package/dist/client/_assets/chunks/457485e72835364662dfead6281638c1-Mv279UyL.woff2 +0 -0
  128. package/dist/client/_assets/chunks/47479c470fae70f10b7c964a7ecbf274-D9VNBclD.woff2 +0 -0
  129. package/dist/client/_assets/chunks/474fac21b12b7efd71f7c321578878b0-BJRz_Yd3.woff2 +0 -0
  130. package/dist/client/_assets/chunks/477866c8396474a17317dcac3e7a014f-e90LWIFy.woff2 +0 -0
  131. package/dist/client/_assets/chunks/478ebdaadda7775c391c5dcab4e697df-RfXJJ1gl.woff2 +0 -0
  132. package/dist/client/_assets/chunks/488846410760fe128dae939836ca5423-D0YbXQOX.woff2 +0 -0
  133. package/dist/client/_assets/chunks/48d6a97a185c799be4fe67aaf7edf213-CqLkiJik.woff2 +0 -0
  134. package/dist/client/_assets/chunks/48eb0a91e50c7f026e248c64145e72af-D8LB2Dye.woff2 +0 -0
  135. package/dist/client/_assets/chunks/490edb9fc8a4356aea556eed32287464-Bxk-XtyT.woff2 +0 -0
  136. package/dist/client/_assets/chunks/4a23fe6e82fd496b5eb20401b6164efe-CfhsfLd6.woff2 +0 -0
  137. package/dist/client/_assets/chunks/4b0e79ba18b2ce424fa93e84996d7cba-B0dS4UvF.woff2 +0 -0
  138. package/dist/client/_assets/chunks/4bc743968cf1c3ce5711de67ef1ccc4d-Dmn20LlT.woff2 +0 -0
  139. package/dist/client/_assets/chunks/4c4bdd0b3f3a52e28f3b643c1c5d43be-BLqyuzyK.woff2 +0 -0
  140. package/dist/client/_assets/chunks/4c96411f3693a9a8657a9c1190f82bce-BhP7AS2j.woff2 +0 -0
  141. package/dist/client/_assets/chunks/4c9aa12aba2a6a57410eacaff7427916-CLDZgzM7.woff2 +0 -0
  142. package/dist/client/_assets/chunks/4cca7233bf8ce5dec2e5d146b993d626-Bf7OgJe6.woff2 +0 -0
  143. package/dist/client/_assets/chunks/4cf0f292f3358bd2f73b1cf4ec1476f3-CIfjw85D.woff2 +0 -0
  144. package/dist/client/_assets/chunks/4d0a9128d06ea857f203bf5d007b1ab9-BZ-BuqS9.woff2 +0 -0
  145. package/dist/client/_assets/chunks/4dc0728df0f2ba70796f45f05654c7ba-DGd3NDaT.woff2 +0 -0
  146. package/dist/client/_assets/chunks/4dc2bc2c55b47f57d13b63aa6b1c8bd4-g3WbclK2.woff2 +0 -0
  147. package/dist/client/_assets/chunks/4e1cc6aafb411b572c8d3511e925ecf1-BUf15jYj.woff2 +0 -0
  148. package/dist/client/_assets/chunks/4e5384920bbb155d9d8d74887b09ea5b-2KLteQXT.woff2 +0 -0
  149. package/dist/client/_assets/chunks/501f66f24bce8234441954de1b568403-CIe4N6pO.woff2 +0 -0
  150. package/dist/client/_assets/chunks/50cfd672bfa62512ba090420acf35c87-CibXrwCT.woff2 +0 -0
  151. package/dist/client/_assets/chunks/5227dbe9933760a48baff21ebd13fc98-v8CBITMX.woff2 +0 -0
  152. package/dist/client/_assets/chunks/526b263e72c189f4b065738aaa6d423a-Dj5Ns6Ev.woff2 +0 -0
  153. package/dist/client/_assets/chunks/53a88404451448cd2e620a0ca0e45a20-CI0P5NCv.woff2 +0 -0
  154. package/dist/client/_assets/chunks/54da934819a917f561b439bfd10f88b6-CpoNRhz7.woff2 +0 -0
  155. package/dist/client/_assets/chunks/54e301f412730f391225db59dae1c8d5-CxTAz9F1.woff2 +0 -0
  156. package/dist/client/_assets/chunks/551b1d7a0b80c8d42af09863cdca7f01-BIGKH8GS.woff2 +0 -0
  157. package/dist/client/_assets/chunks/555d990ab3fd7d3d66c6d1fa9a82fec5-BndAe6f2.woff2 +0 -0
  158. package/dist/client/_assets/chunks/557cd00c5d6827e13d72a0c71b23587b-C6gpr-g-.woff2 +0 -0
  159. package/dist/client/_assets/chunks/563fa31542d553f25abab65cf7f81e1d-Cl_1me5X.woff2 +0 -0
  160. package/dist/client/_assets/chunks/56e1c4734bbbb38af2fbc262bf6e98f2-DZ32amUx.woff2 +0 -0
  161. package/dist/client/_assets/chunks/5947f5da5da9a352a2b534ee64bfc29a-DT0Fj-SL.woff2 +0 -0
  162. package/dist/client/_assets/chunks/5979c33a7eb5963bf8e83e46931b5fb5--4f9yv1z.woff2 +0 -0
  163. package/dist/client/_assets/chunks/597d69d0710e0178b162afb0a0c20401-CpThXFdo.woff2 +0 -0
  164. package/dist/client/_assets/chunks/59966ee0b069b577510fe68c350da0ee-Bs_HDvyP.woff2 +0 -0
  165. package/dist/client/_assets/chunks/5a10741e41259e235841440394c0763d-uCZGOg0A.woff2 +0 -0
  166. package/dist/client/_assets/chunks/5bfc7a121c35ae42623ef804fb525e0e-mS7vyFFz.woff2 +0 -0
  167. package/dist/client/_assets/chunks/5cc23a76e122d0ad2f7cede41bc35b27-4T1ZVQJu.woff2 +0 -0
  168. package/dist/client/_assets/chunks/5d48855bed5f3554eff91b573d7376ac-CPzsdNDF.woff2 +0 -0
  169. package/dist/client/_assets/chunks/5ddcbe564b29ef08632e1aeb33455435-CQzDKa-z.woff2 +0 -0
  170. package/dist/client/_assets/chunks/5f90024544c2907c6c0203c6210c50be-C_1uxIky.woff2 +0 -0
  171. package/dist/client/_assets/chunks/605667a998e91e2b6a4a3cd7c31fe5a9-Co2lOjER.woff2 +0 -0
  172. package/dist/client/_assets/chunks/60a14064ed334f0155795d795e926abe-C2HuVyw5.woff2 +0 -0
  173. package/dist/client/_assets/chunks/60d8b0805a0a8c54a6cca216004beff5-CV1GX5br.woff2 +0 -0
  174. package/dist/client/_assets/chunks/611b62d5fd9698d9b5ce495ba6f14c93-CPi2xRKF.woff2 +0 -0
  175. package/dist/client/_assets/chunks/61bf4287453da4025d03fa6b2dba66ca-CMh4XzzB.woff2 +0 -0
  176. package/dist/client/_assets/chunks/638369541268ed5a10af97ad77498c73-DrpvGu-L.woff2 +0 -0
  177. package/dist/client/_assets/chunks/649b12d7cee7bb981842946e4547e6ca-EACt8KkC.woff2 +0 -0
  178. package/dist/client/_assets/chunks/653bef2ed891ae48d8ed712283080649-C3d1ZTAc.woff2 +0 -0
  179. package/dist/client/_assets/chunks/67d2a81f06ba352f17fbdc3a5e6ea59e-DC8Iostn.woff2 +0 -0
  180. package/dist/client/_assets/chunks/67f32ceea9e78e5109f87724ad886010-C_3-9sSn.woff2 +0 -0
  181. package/dist/client/_assets/chunks/68304f3229cf763465f044fccb5892c0-CTvPlN8c.woff2 +0 -0
  182. package/dist/client/_assets/chunks/687d0f0f90a9b23e40102e16ad8e9836-Bzlx1W-7.woff2 +0 -0
  183. package/dist/client/_assets/chunks/688a88911e4da17b609196a959b8b930-BgwP4zIQ.woff2 +0 -0
  184. package/dist/client/_assets/chunks/68f2fab82ec8e9291f08c3145111549c-CXqagiiH.woff2 +0 -0
  185. package/dist/client/_assets/chunks/69519ada3f3f74ca20aacb8af48ab6b4-DncDOElk.woff2 +0 -0
  186. package/dist/client/_assets/chunks/6a6e884fb2b65ec5b4a3d5ecd0d01a6a-DBNJo4jq.woff2 +0 -0
  187. package/dist/client/_assets/chunks/6db6ddf72c38a78ce44c1327701152e1-CFUWMaQQ.woff2 +0 -0
  188. package/dist/client/_assets/chunks/6e1a8b45b01939088c3a8cfcf8c10681-BemqZSaI.woff2 +0 -0
  189. package/dist/client/_assets/chunks/6e2164fad867d166de2e5b274f04a563-_YD_QMym.woff2 +0 -0
  190. package/dist/client/_assets/chunks/6e83fe0b6e708eaf1c3003d6dee11488-CRqY8z2g.woff2 +0 -0
  191. package/dist/client/_assets/chunks/6eefc9d430171c1e1e4034ecadee31c8-d3VCnQ01.woff2 +0 -0
  192. package/dist/client/_assets/chunks/70861376e5d4f92f8aa7aa1b2749b617-BqwxmSo2.woff2 +0 -0
  193. package/dist/client/_assets/chunks/70adaf50c56d5ff859c64d35e0f1e34e-BCmTRExx.woff2 +0 -0
  194. package/dist/client/_assets/chunks/7124d150570d39ced8d45507dc11ca1e-Co_mUkqP.woff2 +0 -0
  195. package/dist/client/_assets/chunks/71eafb8fbe3a734283517e230ad8b6db-CFmZ-qAS.woff2 +0 -0
  196. package/dist/client/_assets/chunks/72ee453ac0e19bd2c631c8921c44e3de-CEidyyfI.woff2 +0 -0
  197. package/dist/client/_assets/chunks/751f54dbb115140d5b645a6ba4aff5d3-DY-S96zI.woff2 +0 -0
  198. package/dist/client/_assets/chunks/753b5f6fb254bacb6618ace25af3df60-uBG6c32o.woff2 +0 -0
  199. package/dist/client/_assets/chunks/7609e7e74dd4d916a7abc7ecc7d95f7e-CzrNhght.woff2 +0 -0
  200. package/dist/client/_assets/chunks/76b9d6fe838ae4151d95ce7200aa2bf6-BfQWDykU.woff2 +0 -0
  201. package/dist/client/_assets/chunks/76d4244186d118eea245d1385a4de2ec-CEoql1pw.woff2 +0 -0
  202. package/dist/client/_assets/chunks/77a7533bd21ccd33192d142a93555aa8-BzSb8ker.woff2 +0 -0
  203. package/dist/client/_assets/chunks/78ce29fed872e44fc9014d94875d2aac-D1BDuJXp.woff2 +0 -0
  204. package/dist/client/_assets/chunks/79a7fdf7d9c722b5723ae25e6ff8e203-5rPAnIr7.woff2 +0 -0
  205. package/dist/client/_assets/chunks/79a85a253e9b3f12d2e2cb15e16b3003-DTjWGv2X.woff2 +0 -0
  206. package/dist/client/_assets/chunks/7a86b155111ba20f3e87306ff6beac77-DchSNxJ1.woff2 +0 -0
  207. package/dist/client/_assets/chunks/7b1e76975b0984e6f83e3f9f8069e784-OB_rGzsR.woff2 +0 -0
  208. package/dist/client/_assets/chunks/7b6c60131822a0e4d36d980d52509d4e-8dQVuUiW.woff2 +0 -0
  209. package/dist/client/_assets/chunks/7caa14a095a6bc313aab780fe4ff7999-BJ-80Ois.woff2 +0 -0
  210. package/dist/client/_assets/chunks/7cbda564cb2dd4799ab9e89d51286aa7-MlT7YRSz.woff2 +0 -0
  211. package/dist/client/_assets/chunks/7d138084cf03c14116b11297fce0e3e3-CdWCcyam.woff2 +0 -0
  212. package/dist/client/_assets/chunks/7d65a3d6a65050eb5e6eca43398aeba4-CYB359u-.woff2 +0 -0
  213. package/dist/client/_assets/chunks/7dfc711962c8771f97e7c8898a6bcb65-Yv69yrg4.woff2 +0 -0
  214. package/dist/client/_assets/chunks/7ef123b62d530fcba73974fa265e0aae-CD7IMlBc.woff2 +0 -0
  215. package/dist/client/_assets/chunks/7f60eefa15956d6f06dd92404887d58c-LqioDjlc.woff2 +0 -0
  216. package/dist/client/_assets/chunks/7f8c15e0ecb102738981d9fa4cb6b921-8qiw7Fmn.woff2 +0 -0
  217. package/dist/client/_assets/chunks/80466082a896fd328f30a78593c7c568-BMAVDyAy.woff2 +0 -0
  218. package/dist/client/_assets/chunks/812b5a4b87f3a7b4afc1cfebc864f413-B4_mWnLK.woff2 +0 -0
  219. package/dist/client/_assets/chunks/812dfb7f8144d01b3cc9d5ce0b472f40-DxhfKDhh.woff2 +0 -0
  220. package/dist/client/_assets/chunks/84742b1ede4f0bb6d27131298eba21b4-4_QYQ-wj.woff2 +0 -0
  221. package/dist/client/_assets/chunks/8555f0285e3d28e95e2fc0ccccd9caff-CEnfoaRy.woff2 +0 -0
  222. package/dist/client/_assets/chunks/880162ae92cd9e120eb4e4e11fae459d-Cj61LKOn.woff2 +0 -0
  223. package/dist/client/_assets/chunks/8a3c84b0df36f851f5fea75ee8757951-CfHrA-x8.woff2 +0 -0
  224. package/dist/client/_assets/chunks/8b0c8c9f8cfa9fa090d97c5a5efb1f4c-DZc3EGF4.woff2 +0 -0
  225. package/dist/client/_assets/chunks/8c8393bc875f1ee36697a2113f4421ea-CnBugeCf.woff2 +0 -0
  226. package/dist/client/_assets/chunks/8dc035a34c76e6515ca203e2df182588-B1reQPCB.woff2 +0 -0
  227. package/dist/client/_assets/chunks/8e04e64c8f68d292a18d4160fbde8671-DkT7v-sd.woff2 +0 -0
  228. package/dist/client/_assets/chunks/8e6c9bb43afb8cbbff7cf1055e67c9bd-c7wzPI7v.woff2 +0 -0
  229. package/dist/client/_assets/chunks/8eb06109812cb80be44f47b8179c2709-DOaU0xD8.woff2 +0 -0
  230. package/dist/client/_assets/chunks/8f2b960c2823670e94f5b08aa65657e6-CHT-Gunr.woff2 +0 -0
  231. package/dist/client/_assets/chunks/8f89f57230d184f92a36e241874229d7-D6P0FMCn.woff2 +0 -0
  232. package/dist/client/_assets/chunks/904324af375d5fd370af1054355a050e-CHiXmgO7.woff2 +0 -0
  233. package/dist/client/_assets/chunks/90ac4f9d2aa02afdace2843b49fc18bb-CWArmWzF.woff2 +0 -0
  234. package/dist/client/_assets/chunks/90b6f57d77847f512fd11db74fa912f1-DzYbq8hW.woff2 +0 -0
  235. package/dist/client/_assets/chunks/911a2092d64d6d6494b254d819af2b91-C0xmZ-Cu.woff2 +0 -0
  236. package/dist/client/_assets/chunks/913759e6690f9fc0746a20b96f4bdcb4-B0k9hqx0.woff2 +0 -0
  237. package/dist/client/_assets/chunks/9154e26efe532a85a27d80902f5a2d6c-DJQmzPmB.woff2 +0 -0
  238. package/dist/client/_assets/chunks/9180de34b48b325200a97e267befff32-ClJx20hD.woff2 +0 -0
  239. package/dist/client/_assets/chunks/94e7ed67f1557b76fead6b6e456a0415-CjAhhvRn.woff2 +0 -0
  240. package/dist/client/_assets/chunks/95127a92346c04fec1fa81d6295b0a28-DNbeXPGm.woff2 +0 -0
  241. package/dist/client/_assets/chunks/958efb9b2fa2ea0008ffef009885f9f8-CNOwBDS1.woff2 +0 -0
  242. package/dist/client/_assets/chunks/95df3b9f681d9df411c30aea5b24f2e0-Dv7GHFVj.woff2 +0 -0
  243. package/dist/client/_assets/chunks/975af5a496e8d87d821910aa9fe4d598-CG5KDMOI.woff2 +0 -0
  244. package/dist/client/_assets/chunks/97a874bbf55ce89a4ab7cd27c7e938b1-fhost-bL.woff2 +0 -0
  245. package/dist/client/_assets/chunks/9ca9b71010a5faeee7047ef97aeee13b-K6NP7P3o.woff2 +0 -0
  246. package/dist/client/_assets/chunks/9cd0b77920b9d6c64eb686493123fc76-Db6wX5BE.woff2 +0 -0
  247. package/dist/client/_assets/chunks/9de02d745b8e25c6411fb152fb067748-CGxZtq8G.woff2 +0 -0
  248. package/dist/client/_assets/chunks/9eb33a430058d839ebbe2af4b2e0daa9-Cjd-WSds.woff2 +0 -0
  249. package/dist/client/_assets/chunks/9ebd27835ffcbd794e67151ab046ce68-DmxtZVr5.woff2 +0 -0
  250. package/dist/client/_assets/chunks/9f5a73aa8ba417688019d628f334db07-P03uxox_.woff2 +0 -0
  251. package/dist/client/_assets/chunks/9fbc06b2e3ff16b9d705c76db563ef17-XDHSv5Te.woff2 +0 -0
  252. package/dist/client/_assets/chunks/9fd53607094e329fa8e5c785b3ff0f1a-B-9qgOdM.woff2 +0 -0
  253. package/dist/client/_assets/chunks/a077f51cfb5cffb4ff4d8e229c0e9e79-BaPgyZ5H.woff2 +0 -0
  254. package/dist/client/_assets/chunks/a0f0c06d5c7a3ffa97706178cce212a8-DN_NE4Ic.woff2 +0 -0
  255. package/dist/client/_assets/chunks/a38c1830367f784181b6f544b0b11bbd-BmPGDXn1.woff2 +0 -0
  256. package/dist/client/_assets/chunks/a397997b579d3945c9c70a979c17a8ad-DXa54wGz.woff2 +0 -0
  257. package/dist/client/_assets/chunks/a3b929542e6c5a0644b73a7c8a8b6c03-B5i2p-Hj.woff2 +0 -0
  258. package/dist/client/_assets/chunks/a578742770fcd2226e3c45b5b6efdcb0-C1QhQdHg.woff2 +0 -0
  259. package/dist/client/_assets/chunks/a68d9d5027803832bb28e78cdcd04949-D_bwWhwg.woff2 +0 -0
  260. package/dist/client/_assets/chunks/a8857f5d478f101c053ba02d2f223e90-CHOaRPpq.woff2 +0 -0
  261. package/dist/client/_assets/chunks/a904b05966368bcf90b632c7c2e5f76b-BbTXYQVh.woff2 +0 -0
  262. package/dist/client/_assets/chunks/a9cf85e27428c14351d30eac8cbc8d91-DMaVc3-8.woff2 +0 -0
  263. package/dist/client/_assets/chunks/aa0ce6740f301351761a0615cc8b2e99-4RL3pitV.woff2 +0 -0
  264. package/dist/client/_assets/chunks/aa218a2c45f3749537ce876201e5152b-DndHqA-p.woff2 +0 -0
  265. package/dist/client/_assets/chunks/aa28db16818f9eaa8c817f289e1c3270-Drm1Jwhe.woff2 +0 -0
  266. package/dist/client/_assets/chunks/aa64c9953af43ca65832f413895bb433-CR32RteD.woff2 +0 -0
  267. package/dist/client/_assets/chunks/aa96d698491c2540e2dcf7009c65c456-BXlPVR0v.woff2 +0 -0
  268. package/dist/client/_assets/chunks/ada8f0241244c60ec8d3d59ad37f20a5-BFQsznEm.woff2 +0 -0
  269. package/dist/client/_assets/chunks/ae25c41034ddc1a9e0b41f5034c9aa4b-COQJkHOc.woff2 +0 -0
  270. package/dist/client/_assets/chunks/ae289ae3f8cdb54a3a6c07174517afec-DFGIoTY6.woff2 +0 -0
  271. package/dist/client/_assets/chunks/ae401fb4db80d5ff5cd3f8d9bc811070-Byt9AwoT.woff2 +0 -0
  272. package/dist/client/_assets/chunks/b02dfa2aa52cbdb1b2f11a9f44335469-DVWsuPSL.woff2 +0 -0
  273. package/dist/client/_assets/chunks/b0ab3a7f319ce6dd3c9a4de2674e7c72-CksmA6Hr.woff2 +0 -0
  274. package/dist/client/_assets/chunks/b159deb135e9946eea0572d52778170b-BZrtOTn0.woff2 +0 -0
  275. package/dist/client/_assets/chunks/b2e326f7f9b807451bf9c745df747efe-BvlONCPB.woff2 +0 -0
  276. package/dist/client/_assets/chunks/b341de0bc0bfe194a6c28dcfb566029e-DJAj3r6U.woff2 +0 -0
  277. package/dist/client/_assets/chunks/b846c293981ca5429eabaa967f222f26-GS-TAaO6.woff2 +0 -0
  278. package/dist/client/_assets/chunks/b91450304d9ac44f5c6e0da0792e055d-vS0S7kl0.woff2 +0 -0
  279. package/dist/client/_assets/chunks/baa325551b381c5e035ef143e56d4abe-DJg3GhKJ.woff2 +0 -0
  280. package/dist/client/_assets/chunks/bc3f0cb8b55ee11d32b94ca488976f8d-Dp5KZMSD.woff2 +0 -0
  281. package/dist/client/_assets/chunks/bcb3307527d6d0033bf0f17660b91e71-BMgRW_eW.woff2 +0 -0
  282. package/dist/client/_assets/chunks/be64f9379412876e00fd3a0bfa6b6fe9-BJ3FMWFM.woff2 +0 -0
  283. package/dist/client/_assets/chunks/befed8a4fa817773fa7109db6fe07f56-3sAmDsOO.woff2 +0 -0
  284. package/dist/client/_assets/chunks/bf1acc86e17b4229c548828a9d6f455d-JNuPCGDM.woff2 +0 -0
  285. package/dist/client/_assets/chunks/c09ee2b219982f8d46ad9968b7e6e0ba-qVb8yJHb.woff2 +0 -0
  286. package/dist/client/_assets/chunks/c0c7836749e585cee24ab2f8457c5b01-Ds0003oT.woff2 +0 -0
  287. package/dist/client/_assets/chunks/c2ac4ef1860812036ca2b8c4e4089bdc-DsziO8Du.woff2 +0 -0
  288. package/dist/client/_assets/chunks/c31019c08bd22464f7a88f090281404c-CUC0jMW1.woff2 +0 -0
  289. package/dist/client/_assets/chunks/c33c59feccf391f0c5f1f5d24e36d1fe-BXTVggad.woff2 +0 -0
  290. package/dist/client/_assets/chunks/c39ec937c6a8d124e8b68cf829ea5ad4-CarrkdmQ.woff2 +0 -0
  291. package/dist/client/_assets/chunks/c3fbc1f2557c343863a10698f8c966a2-BiOC1yo2.woff2 +0 -0
  292. package/dist/client/_assets/chunks/c3fd21315345ae541f6e98067059fa19-rHtO7vK5.woff2 +0 -0
  293. package/dist/client/_assets/chunks/c568a16e3168ceb1f191b70022c492ea-BpAiyU80.woff2 +0 -0
  294. package/dist/client/_assets/chunks/c57ee3b49b7e45b995539a6b2c51f138-IHDXeVRU.woff2 +0 -0
  295. package/dist/client/_assets/chunks/c5c1c0be944ea39a3f50a02d32f5b759-DAwbrvTL.woff2 +0 -0
  296. package/dist/client/_assets/chunks/c5e66d60be3375835bbd8d6b797f6eac-DWp4tpvN.woff2 +0 -0
  297. package/dist/client/_assets/chunks/c5f1075caf6d1344ee720de85114a521-DTTbeLhd.woff2 +0 -0
  298. package/dist/client/_assets/chunks/c82fd9456d7465b5e5bd3659e9b14c55-Cs6h6hE6.woff2 +0 -0
  299. package/dist/client/_assets/chunks/c90b7b65d2b9696fbf3a506738f94d68-DsvgiuQQ.woff2 +0 -0
  300. package/dist/client/_assets/chunks/cae29b3f8951eaf20d2f61c2206e28d9-CoHXmhmG.woff2 +0 -0
  301. package/dist/client/_assets/chunks/cba6ad3981cb7861428d4be169ee8124-CBdFsPAF.woff2 +0 -0
  302. package/dist/client/_assets/chunks/cc26525aa2af1f0b929af32ce50a7fba-D55LVtW9.woff2 +0 -0
  303. package/dist/client/_assets/chunks/cd0e7b51eddb22a77a09b025c0281434-ByI_TSTq.woff2 +0 -0
  304. package/dist/client/_assets/chunks/cd10a3af2133805d8c92104d1ee6ff18-Dtr5mBXe.woff2 +0 -0
  305. package/dist/client/_assets/chunks/cd6d074f3957d58bac58437fc97e5e33-CrVZuSbw.woff2 +0 -0
  306. package/dist/client/_assets/chunks/cd75ca47da9ae4c0899e37d4c543319b-DXU1s4Ja.woff2 +0 -0
  307. package/dist/client/_assets/chunks/cef249b6d013fb0cc0d574176bc23811-BL7WfNHA.woff2 +0 -0
  308. package/dist/client/_assets/chunks/d043b8d7a48bb0ac59ee1f1477d88eee-d5CwY2I8.woff2 +0 -0
  309. package/dist/client/_assets/chunks/d0bd387fda28e58d3c9b3efa2468dd8a-CjDFowFS.woff2 +0 -0
  310. package/dist/client/_assets/chunks/d12ce1d8445213317f9163283e58a05d-4RtGQnak.woff2 +0 -0
  311. package/dist/client/_assets/chunks/d15a3317942b7d31978a759fbf2222c8-DQ9QFQdS.woff2 +0 -0
  312. package/dist/client/_assets/chunks/d28fb13acf9ced9f0657fd4012c81cd2-D8Bgk6sd.woff2 +0 -0
  313. package/dist/client/_assets/chunks/d320b000b5978c7251148a6a154741b8-CcGZjeFx.woff2 +0 -0
  314. package/dist/client/_assets/chunks/d3714e6b90de8e2085dfb2514464dd6a-8LsVAcmb.woff2 +0 -0
  315. package/dist/client/_assets/chunks/d3beff96216c8af1aa79246476b6a323-hQysoMK3.woff2 +0 -0
  316. package/dist/client/_assets/chunks/d3e311f30c811dc339c262a79a51877e-CkUTbS74.woff2 +0 -0
  317. package/dist/client/_assets/chunks/d51f4cdc83711e510f5d25f03235597e-BY3VAGvr.woff2 +0 -0
  318. package/dist/client/_assets/chunks/d5df4a8dfd4328c67d933b3912c6ad0f-A2fccswC.woff2 +0 -0
  319. package/dist/client/_assets/chunks/d740dc2e854aaa7b3dcdd3ed25455eeb-Cw8LrB06.woff2 +0 -0
  320. package/dist/client/_assets/chunks/d8325ba7ae651bc30440905bd67b95f1-Dg8M_ijr.woff2 +0 -0
  321. package/dist/client/_assets/chunks/da13b136efb1d1e4c76575af8f79a698-BbfzLGVB.woff2 +0 -0
  322. package/dist/client/_assets/chunks/da2cf0ec56bf69374ee37764c7e3ea3d-fyVBKE2g.woff2 +0 -0
  323. package/dist/client/_assets/chunks/da93ae099ff3b7aae27b3f674d3fc721-BDG5ywtd.woff2 +0 -0
  324. package/dist/client/_assets/chunks/daf62255dd60679946f28c442ca62533-BnIjYSn3.woff2 +0 -0
  325. package/dist/client/_assets/chunks/dccda6a2e2db3b530788bdfa2acd1979-ByL7eZcn.woff2 +0 -0
  326. package/dist/client/_assets/chunks/dd01a1035345f6921a48525b8ce08f06-vkP6t4fC.woff2 +0 -0
  327. package/dist/client/_assets/chunks/e2204cf85edcb96c5de5c3dcf240da9d-Ch3zKjD-.woff2 +0 -0
  328. package/dist/client/_assets/chunks/e264213b9e102dabc603adb6e4fda5e6-CyxmQBHB.woff2 +0 -0
  329. package/dist/client/_assets/chunks/e3e913e145ddcd9323b2a0972967feb6-3sjEdzck.woff2 +0 -0
  330. package/dist/client/_assets/chunks/e4fb59479cedc87ba79785590bf861ca-sx7RZ8Vu.woff2 +0 -0
  331. package/dist/client/_assets/chunks/e5d00355f73293d40b61299459d17ca5-D8Vt3D-z.woff2 +0 -0
  332. package/dist/client/_assets/chunks/e647b8d2efc501c0cc0e407249cc7135-BrSW9DU4.woff2 +0 -0
  333. package/dist/client/_assets/chunks/e6e60ffb2ebd1828628764b507060aea-Dqyf48QC.woff2 +0 -0
  334. package/dist/client/_assets/chunks/e7c7ef3669ae48c0a736f06ca471e1d7-DNCebEB-.woff2 +0 -0
  335. package/dist/client/_assets/chunks/e81a742cacef744130c40de1b90837d8-DtAIFBk_.woff2 +0 -0
  336. package/dist/client/_assets/chunks/e8b755172122d1d0a5dd453e96b0ff24-BNTSyPYU.woff2 +0 -0
  337. package/dist/client/_assets/chunks/e99280299c305402eaa5271a3e36c49b-BpqXwl7U.woff2 +0 -0
  338. package/dist/client/_assets/chunks/e9c66b085052ece66bfadf45f711d3e1-DVza3Uob.woff2 +0 -0
  339. package/dist/client/_assets/chunks/ed7c6dafaa6d8bcf015ef0ca574837df-C7bcndJl.woff2 +0 -0
  340. package/dist/client/_assets/chunks/f00eb499abb94fa7b799d6d8c9b050e9-D_XvVTI9.woff2 +0 -0
  341. package/dist/client/_assets/chunks/f2900a1d30c3a33129f4e2225669bd0e-in84LilX.woff2 +0 -0
  342. package/dist/client/_assets/chunks/f2fb1f1fbf7e44afb53c672ec286a22e-tFk0sTQc.woff2 +0 -0
  343. package/dist/client/_assets/chunks/f372129c60aaece937cf7b91ee75c9b8-DiP_JYOt.woff2 +0 -0
  344. package/dist/client/_assets/chunks/f5237486197aeff59341a1ff38b8eff8-D_uuqrNZ.woff2 +0 -0
  345. package/dist/client/_assets/chunks/f553d54ef931066712d8f3f0ce018e1b-D8K983U5.woff2 +0 -0
  346. package/dist/client/_assets/chunks/f5d7487963d43c89da63aaf10f2e6fb7-B7GIrfPU.woff2 +0 -0
  347. package/dist/client/_assets/chunks/f649cba8e14c33d6bf2265483b14b895-Nm5wSov8.woff2 +0 -0
  348. package/dist/client/_assets/chunks/f6839df1bf7cb4dc8d27e5ea55bbe633-BIjWKN84.woff2 +0 -0
  349. package/dist/client/_assets/chunks/f6b7304e028980f77a7f7007bb540abd-CIlBt6sf.woff2 +0 -0
  350. package/dist/client/_assets/chunks/f75496953a40ff241178240209f56990-MOSKd67d.woff2 +0 -0
  351. package/dist/client/_assets/chunks/f7d36ffff7a75c9c6216d576a57dd00d-CaXQA5A8.woff2 +0 -0
  352. package/dist/client/_assets/chunks/f7f3f63e7a149cd89eccab3b52171d05-oNOOSdDV.woff2 +0 -0
  353. package/dist/client/_assets/chunks/f8db8bef0a6e1178835d350ae0d384a1-ZAiKM369.woff2 +0 -0
  354. package/dist/client/_assets/chunks/f92d74d1d217d21b39075ff23f79f7fd-BlTe0_IX.woff2 +0 -0
  355. package/dist/client/_assets/chunks/f9d6d981d8b87b3e469027277f585741-DQqIv77a.woff2 +0 -0
  356. package/dist/client/_assets/chunks/fa7d3b99744d7f2dc9e00864a97a62d6-Pfww07DK.woff2 +0 -0
  357. package/dist/client/_assets/chunks/fa8ed469ef290bfeb571418fe0abb628-D5v-Z7kY.woff2 +0 -0
  358. package/dist/client/_assets/chunks/fb0e90665980954719c2eb685b130bc0-CiWRvo58.woff2 +0 -0
  359. package/dist/client/_assets/chunks/fb4649a82c50620773d79820e2e5ff13-dw14czgx.woff2 +0 -0
  360. package/dist/client/_assets/chunks/fb61b690208eff56e6d8432951270901-CUAnVvWa.woff2 +0 -0
  361. package/dist/client/_assets/chunks/fb9402d6c6357a825affc402f14d5a7e-CIp4G53L.woff2 +0 -0
  362. package/dist/client/_assets/chunks/fcc41f6a067ddd658bba5c9dff234a32-BtZTNo2r.woff2 +0 -0
  363. package/dist/client/_assets/chunks/fd6ad889fcf3583bd9b0b6db53aad434-DqiKyYmz.woff2 +0 -0
  364. package/dist/client/_assets/chunks/ff0937ad63cda71ff420945ead55ab4d-CXGPdnOz.woff2 +0 -0
  365. package/dist/client/_assets/chunks/heic-to-XcUDQvtx.js +1 -0
  366. package/dist/client/_assets/chunks/literata-cyrillic-ext-wght-normal-CGKlZYBf.woff2 +0 -0
  367. package/dist/client/_assets/chunks/literata-cyrillic-wght-normal-DLqwHbi6.woff2 +0 -0
  368. package/dist/client/_assets/chunks/literata-greek-ext-wght-normal-e3e57Shi.woff2 +0 -0
  369. package/dist/client/_assets/chunks/literata-greek-wght-normal-CO1l-giJ.woff2 +0 -0
  370. package/dist/client/_assets/chunks/literata-latin-ext-wght-normal-BnEbWgdZ.woff2 +0 -0
  371. package/dist/client/_assets/chunks/literata-latin-wght-normal-DLxlUchJ.woff2 +0 -0
  372. package/dist/client/_assets/chunks/literata-vietnamese-wght-normal-LcSrhZ7T.woff2 +0 -0
  373. package/dist/client/_assets/chunks/module-ChVQstFd.js +716 -0
  374. package/dist/client/_assets/chunks/native-CR5HLOyf.js +1 -0
  375. package/dist/client/_assets/chunks/news-cycle-cyrillic-400-normal-9BSXki1I.woff +0 -0
  376. package/dist/client/_assets/chunks/news-cycle-cyrillic-400-normal-CUYmhJri.woff2 +0 -0
  377. package/dist/client/_assets/chunks/news-cycle-cyrillic-ext-400-normal-CKyb9yaQ.woff2 +0 -0
  378. package/dist/client/_assets/chunks/news-cycle-cyrillic-ext-400-normal-Llxpm7uO.woff +0 -0
  379. package/dist/client/_assets/chunks/news-cycle-greek-400-normal-BiAZ91Zl.woff2 +0 -0
  380. package/dist/client/_assets/chunks/news-cycle-greek-400-normal-D88oNngQ.woff +0 -0
  381. package/dist/client/_assets/chunks/news-cycle-greek-ext-400-normal-Ckclqjrc.woff2 +0 -0
  382. package/dist/client/_assets/chunks/news-cycle-greek-ext-400-normal-oz-bBDWj.woff +0 -0
  383. package/dist/client/_assets/chunks/news-cycle-latin-400-normal-BLgpQ3uo.woff +0 -0
  384. package/dist/client/_assets/chunks/news-cycle-latin-400-normal-BgW97ttO.woff2 +0 -0
  385. package/dist/client/_assets/chunks/news-cycle-latin-ext-400-normal-DmDltzLi.woff2 +0 -0
  386. package/dist/client/_assets/chunks/news-cycle-latin-ext-400-normal-lewDoXxP.woff +0 -0
  387. package/dist/client/_assets/chunks/news-cycle-vietnamese-400-normal-QPSuG-pc.woff +0 -0
  388. package/dist/client/_assets/chunks/news-cycle-vietnamese-400-normal-t2mxI9x9.woff2 +0 -0
  389. package/dist/client/_assets/chunks/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
  390. package/dist/client/_assets/chunks/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
  391. package/dist/client/_assets/chunks/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
  392. package/dist/client/_assets/chunks/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
  393. package/dist/client/_assets/chunks/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
  394. package/dist/client/_assets/chunks/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
  395. package/dist/client/_assets/chunks/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
  396. package/dist/client/_assets/chunks/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
  397. package/dist/client/_assets/chunks/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
  398. package/dist/client/_assets/chunks/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
  399. package/dist/client/_assets/chunks/url-CG0eolsk.js +1 -0
  400. package/dist/client/_assets/client-auth.js +3260 -0
  401. package/dist/client/_assets/client-cjk-tc.css +1 -0
  402. package/dist/client/_assets/client-cjk.css +1 -0
  403. package/dist/client/_assets/client.css +2 -0
  404. package/dist/client/_assets/client.js +380 -0
  405. package/dist/index.js +2 -20359
  406. package/dist/node.js +479 -0
  407. package/package.json +67 -40
  408. package/src/__tests__/bin/uploads-cleanup.test.ts +77 -0
  409. package/src/__tests__/export-service.test.ts +550 -0
  410. package/src/__tests__/helpers/app.ts +60 -27
  411. package/src/__tests__/helpers/db.ts +14 -24
  412. package/src/__tests__/helpers/lingui-core-macro-mock.ts +15 -2
  413. package/src/__tests__/import-site-command.test.ts +406 -0
  414. package/src/__tests__/site-localize-media.test.ts +150 -0
  415. package/src/__tests__/site-media-parser.test.ts +216 -0
  416. package/src/app.tsx +277 -175
  417. package/src/assets/branding/generated/README.txt +15 -0
  418. package/src/assets/branding/generated/jant-apple-touch-icon.png +0 -0
  419. package/src/assets/branding/generated/jant-brand-assets.zip +0 -0
  420. package/src/assets/branding/generated/jant-brand-tile-512.png +0 -0
  421. package/src/assets/branding/generated/jant-brand-tile.svg +1 -0
  422. package/src/assets/branding/generated/jant-circle-tile-512.png +0 -0
  423. package/src/assets/branding/generated/jant-circle-tile.svg +1 -0
  424. package/src/assets/branding/generated/jant-favicon.ico +0 -0
  425. package/src/assets/branding/generated/jant-logo-negative.svg +1 -0
  426. package/src/assets/branding/generated/jant-logo-positive-512.png +0 -0
  427. package/src/assets/branding/generated/jant-logo-positive.svg +1 -0
  428. package/src/assets/branding/generated/jant-social-preview.png +0 -0
  429. package/src/assets/branding/generated/jant-square-tile-512.png +0 -0
  430. package/src/assets/branding/generated/jant-square-tile.svg +1 -0
  431. package/src/assets/branding/jant-logo-positive.svg +3 -0
  432. package/src/auth.ts +47 -6
  433. package/src/client/__tests__/avatar-upload.test.ts +186 -0
  434. package/src/client/__tests__/collection-form-bridge.test.ts +127 -0
  435. package/src/client/__tests__/collection-page-actions.test.ts +112 -0
  436. package/src/client/__tests__/collection-sort-menu.test.ts +78 -0
  437. package/src/client/__tests__/compose-bridge.test.ts +260 -0
  438. package/src/client/__tests__/compose-discovery.test.ts +155 -0
  439. package/src/client/__tests__/compose-shortcuts.test.ts +133 -0
  440. package/src/client/__tests__/confirm.test.ts +98 -0
  441. package/src/client/__tests__/custom-url-menu.test.ts +104 -0
  442. package/src/client/__tests__/form-enter-submit.test.ts +110 -0
  443. package/src/client/__tests__/post-form-bridge.test.ts +82 -0
  444. package/src/client/__tests__/sortable-list.test.ts +91 -0
  445. package/src/client/__tests__/toast.test.ts +46 -0
  446. package/src/client/archive-nav.js +1 -3
  447. package/src/client/avatar-upload.ts +66 -25
  448. package/src/client/collection-form-bridge.ts +32 -15
  449. package/src/client/collection-page-actions.ts +160 -0
  450. package/src/client/collection-sort-menu.ts +105 -0
  451. package/src/client/components/__tests__/jant-collection-form.test.ts +199 -51
  452. package/src/client/components/__tests__/jant-collection-sidebar.test.ts +306 -0
  453. package/src/client/components/__tests__/jant-compose-dialog.test.ts +2507 -342
  454. package/src/client/components/__tests__/jant-compose-editor.test.ts +484 -20
  455. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +298 -0
  456. package/src/client/components/__tests__/jant-post-form.test.ts +9 -8
  457. package/src/client/components/__tests__/jant-post-menu.test.ts +325 -0
  458. package/src/client/components/__tests__/jant-settings-avatar.test.ts +27 -3
  459. package/src/client/components/__tests__/jant-settings-general.test.ts +312 -84
  460. package/src/client/components/collection-manager-types.ts +51 -0
  461. package/src/client/components/collection-types.ts +12 -10
  462. package/src/client/components/compose-types.ts +80 -13
  463. package/src/client/components/jant-collection-form.ts +340 -509
  464. package/src/client/components/jant-collection-sidebar.ts +474 -463
  465. package/src/client/components/jant-compose-dialog.ts +1566 -479
  466. package/src/client/components/jant-compose-editor.ts +531 -276
  467. package/src/client/components/jant-compose-fullscreen.ts +184 -112
  468. package/src/client/components/jant-confirm-dialog.ts +249 -0
  469. package/src/client/components/jant-nav-manager.ts +79 -103
  470. package/src/client/components/jant-post-form.ts +4 -1
  471. package/src/client/components/jant-post-menu.ts +583 -306
  472. package/src/client/components/jant-settings-avatar.ts +15 -9
  473. package/src/client/components/jant-settings-general.ts +444 -137
  474. package/src/client/components/nav-manager-types.ts +6 -3
  475. package/src/client/components/post-form-template.ts +2 -9
  476. package/src/client/components/post-form-types.ts +2 -4
  477. package/src/client/components/settings-types.ts +34 -2
  478. package/src/client/compose-bridge.ts +297 -171
  479. package/src/client/compose-discovery-bridge.ts +9 -0
  480. package/src/client/compose-discovery.ts +254 -0
  481. package/src/client/compose-launch.ts +98 -0
  482. package/src/client/compose-shortcuts.ts +61 -0
  483. package/src/client/confirm.ts +40 -0
  484. package/src/client/custom-url-menu.ts +128 -0
  485. package/src/client/form-enter-submit.ts +87 -0
  486. package/src/client/json.ts +37 -0
  487. package/src/client/multipart-upload.ts +51 -18
  488. package/src/client/post-form-bridge.ts +20 -14
  489. package/src/client/runtime-paths.ts +72 -0
  490. package/src/client/settings-bridge.ts +54 -10
  491. package/src/client/site-header-nav.js +84 -0
  492. package/src/client/sortable-list.ts +152 -0
  493. package/src/client/thread-context.ts +11 -5
  494. package/src/client/tiptap/__tests__/floating-position.test.ts +74 -0
  495. package/src/client/tiptap/__tests__/link-toolbar.test.ts +94 -0
  496. package/src/client/tiptap/__tests__/paste-media.test.ts +39 -0
  497. package/src/client/tiptap/bubble-menu.ts +98 -13
  498. package/src/client/tiptap/create-editor.ts +6 -0
  499. package/src/client/tiptap/extensions.ts +15 -4
  500. package/src/client/tiptap/floating-position.ts +167 -0
  501. package/src/client/tiptap/inline-image-upload.ts +73 -0
  502. package/src/client/tiptap/link-input-rules.ts +56 -0
  503. package/src/client/tiptap/link-toolbar.ts +60 -14
  504. package/src/client/tiptap/paste-media.ts +120 -0
  505. package/src/client/tiptap/slash-commands.ts +205 -52
  506. package/src/client/tiptap/toolbar-mode.ts +31 -0
  507. package/src/client/toast.ts +136 -16
  508. package/src/client/types/sortablejs.d.ts +4 -4
  509. package/src/client/upload-session.ts +270 -0
  510. package/src/client/upload-with-metadata.ts +10 -17
  511. package/src/client-auth.ts +38 -0
  512. package/src/client.ts +10 -29
  513. package/src/db/__tests__/d1-query.test.ts +84 -0
  514. package/src/db/__tests__/demo-canonical-snapshot.test.ts +78 -0
  515. package/src/db/__tests__/demo-env-loader.test.ts +114 -0
  516. package/src/db/__tests__/dialect.test.ts +42 -0
  517. package/src/db/__tests__/migration-artifacts.test.ts +82 -0
  518. package/src/db/__tests__/migration-rehearsal.test.ts +73 -0
  519. package/src/db/__tests__/migration-runner.test.ts +72 -0
  520. package/src/db/__tests__/migrations.test.ts +293 -8
  521. package/src/db/__tests__/r2-query.test.ts +102 -0
  522. package/src/db/__tests__/wrangler-config.test.ts +86 -0
  523. package/src/db/backfills/0000_normalize_legacy_time_zone_values.sql +38 -0
  524. package/src/db/backfills/README.md +11 -0
  525. package/src/db/dialect.ts +71 -0
  526. package/src/db/index.ts +84 -2
  527. package/src/db/migrations/0000_baseline.sql +35 -57
  528. package/src/db/migrations/0002_site_aware_core.sql +354 -0
  529. package/src/db/migrations/0003_fts_site_aware.sql +38 -0
  530. package/src/db/migrations/0004_perpetual_eternity.sql +29 -0
  531. package/src/db/migrations/meta/0000_snapshot.json +134 -138
  532. package/src/db/migrations/meta/0001_snapshot.json +135 -139
  533. package/src/db/migrations/meta/0002_snapshot.json +1717 -0
  534. package/src/db/migrations/meta/0003_snapshot.json +1717 -0
  535. package/src/db/migrations/meta/0004_snapshot.json +1897 -0
  536. package/src/db/migrations/meta/_journal.json +23 -2
  537. package/src/db/migrations/pg/0000_pg_site_aware_baseline.sql +306 -0
  538. package/src/db/migrations/pg/0001_pg_search_support.sql +8 -0
  539. package/src/db/migrations/pg/0002_breezy_lockjaw.sql +29 -0
  540. package/src/db/migrations/pg/README.md +8 -0
  541. package/src/db/migrations/pg/meta/0000_snapshot.json +2195 -0
  542. package/src/db/migrations/pg/meta/0001_snapshot.json +2248 -0
  543. package/src/db/migrations/pg/meta/0002_snapshot.json +2476 -0
  544. package/src/db/migrations/pg/meta/_journal.json +27 -0
  545. package/src/db/pg/__tests__/node.test.ts +58 -0
  546. package/src/db/pg/node.ts +151 -0
  547. package/src/db/pg/schema.ts +739 -0
  548. package/src/db/raw-query.ts +15 -0
  549. package/src/db/rehearsal-fixtures/demo-current.json +25 -0
  550. package/src/db/rehearsal-fixtures/demo-current.sql +142 -0
  551. package/src/db/schema-bundle.ts +12 -0
  552. package/src/db/schema.ts +309 -128
  553. package/src/db/sqlite/schema.ts +1 -0
  554. package/src/i18n/__tests__/context.test.tsx +35 -0
  555. package/src/i18n/context.tsx +10 -2
  556. package/src/i18n/locales/en.po +1792 -231
  557. package/src/i18n/locales/en.ts +1 -1
  558. package/src/i18n/locales/glossary.zh-Hans.yml +116 -0
  559. package/src/i18n/locales/glossary.zh-Hant.yml +116 -0
  560. package/src/i18n/locales/zh-Hans.po +1831 -270
  561. package/src/i18n/locales/zh-Hans.ts +1 -1
  562. package/src/i18n/locales/zh-Hant.po +1835 -274
  563. package/src/i18n/locales/zh-Hant.ts +1 -1
  564. package/src/index.ts +4 -0
  565. package/src/lib/__tests__/asset-path.test.ts +54 -0
  566. package/src/lib/__tests__/blurhash-placeholder.test.ts +25 -1
  567. package/src/lib/__tests__/collection-groups.test.ts +63 -0
  568. package/src/lib/__tests__/collection-sort.test.ts +45 -0
  569. package/src/lib/__tests__/constants.test.ts +24 -1
  570. package/src/lib/__tests__/favicon.test.ts +1 -1
  571. package/src/lib/__tests__/feed.test.ts +147 -0
  572. package/src/lib/__tests__/hosted-control-plane.test.ts +106 -0
  573. package/src/lib/__tests__/hosted-domain.test.ts +78 -0
  574. package/src/lib/__tests__/hosted-signin.test.ts +103 -0
  575. package/src/lib/__tests__/icons.test.ts +14 -172
  576. package/src/lib/__tests__/image.test.ts +23 -4
  577. package/src/lib/__tests__/jant-branding.test.ts +160 -0
  578. package/src/lib/__tests__/markdown-to-tiptap.test.ts +27 -0
  579. package/src/lib/__tests__/page-title.test.ts +20 -0
  580. package/src/lib/__tests__/password.test.ts +60 -0
  581. package/src/lib/__tests__/post-display.test.ts +99 -0
  582. package/src/lib/__tests__/post-meta.test.ts +126 -0
  583. package/src/lib/__tests__/public-storage.test.ts +75 -0
  584. package/src/lib/__tests__/resolve-config.test.ts +156 -28
  585. package/src/lib/__tests__/schemas.test.ts +286 -22
  586. package/src/lib/__tests__/search-snippet.test.ts +43 -0
  587. package/src/lib/__tests__/site-resolution.test.ts +39 -0
  588. package/src/lib/__tests__/slug-format.test.ts +54 -0
  589. package/src/lib/__tests__/startup-config.test.ts +87 -0
  590. package/src/lib/__tests__/storage.test.ts +218 -1
  591. package/src/lib/__tests__/theme.test.ts +50 -13
  592. package/src/lib/__tests__/time.test.ts +44 -0
  593. package/src/lib/__tests__/timeline.test.ts +492 -4
  594. package/src/lib/__tests__/timezones.test.ts +51 -18
  595. package/src/lib/__tests__/tiptap-to-markdown.test.ts +23 -0
  596. package/src/lib/__tests__/url.test.ts +79 -1
  597. package/src/lib/__tests__/view.test.ts +74 -13
  598. package/src/lib/asset-path.ts +87 -0
  599. package/src/lib/blurhash-placeholder.ts +56 -1
  600. package/src/lib/collection-groups.ts +69 -0
  601. package/src/lib/collection-sort.ts +61 -0
  602. package/src/lib/confirm.ts +37 -0
  603. package/src/lib/constants.ts +20 -3
  604. package/src/lib/crypto.ts +47 -0
  605. package/src/lib/env.ts +245 -0
  606. package/src/lib/errors.ts +25 -9
  607. package/src/lib/favicon.ts +2 -1
  608. package/src/lib/featured-icons.ts +33 -0
  609. package/src/lib/feed.ts +73 -24
  610. package/src/lib/hosted-control-plane-sync.ts +27 -0
  611. package/src/lib/hosted-control-plane.ts +99 -0
  612. package/src/lib/hosted-domain-check.ts +98 -0
  613. package/src/lib/hosted-domain.ts +61 -0
  614. package/src/lib/hosted-signin.ts +115 -0
  615. package/src/lib/hosted-sso.ts +110 -0
  616. package/src/lib/icons.ts +10 -175
  617. package/src/lib/ids.ts +54 -0
  618. package/src/lib/image.ts +18 -7
  619. package/src/lib/jant-branding-generated.ts +947 -0
  620. package/src/lib/jant-branding.ts +520 -0
  621. package/src/lib/markdown-to-tiptap.ts +57 -0
  622. package/src/lib/media-helpers.ts +6 -2
  623. package/src/lib/navigation.ts +9 -2
  624. package/src/lib/page-title.ts +23 -0
  625. package/src/lib/pagination.ts +34 -0
  626. package/src/lib/password.ts +164 -0
  627. package/src/lib/post-display.ts +162 -0
  628. package/src/lib/post-meta.ts +95 -0
  629. package/src/lib/public-storage.ts +57 -0
  630. package/src/lib/render.tsx +22 -0
  631. package/src/lib/resolve-config.ts +78 -27
  632. package/src/lib/schemas.ts +271 -73
  633. package/src/lib/search-snippet.ts +110 -8
  634. package/src/lib/site-resolution.ts +28 -0
  635. package/src/lib/slug-format.ts +92 -0
  636. package/src/lib/slug.ts +1 -1
  637. package/src/lib/startup-config.ts +244 -0
  638. package/src/lib/storage.ts +722 -28
  639. package/src/lib/theme.ts +13 -6
  640. package/src/lib/time.ts +59 -15
  641. package/src/lib/timeline.ts +394 -63
  642. package/src/lib/timezones.ts +120 -50
  643. package/src/lib/tiptap-render.ts +1 -3
  644. package/src/lib/tiptap-to-markdown.ts +49 -0
  645. package/src/lib/upload.ts +216 -11
  646. package/src/lib/url.ts +210 -0
  647. package/src/lib/view.ts +87 -31
  648. package/src/middleware/__tests__/auth.test.ts +216 -14
  649. package/src/middleware/__tests__/error-handler.test.ts +37 -0
  650. package/src/middleware/__tests__/onboarding.test.ts +9 -0
  651. package/src/middleware/__tests__/secure-headers.test.ts +111 -0
  652. package/src/middleware/auth.ts +156 -28
  653. package/src/middleware/config.ts +29 -9
  654. package/src/middleware/error-handler.ts +16 -1
  655. package/src/middleware/onboarding.ts +15 -6
  656. package/src/middleware/secure-headers.ts +136 -25
  657. package/src/node/__tests__/cli-db-execute-file.test.ts +109 -0
  658. package/src/node/__tests__/cli-deploy.test.ts +124 -0
  659. package/src/node/__tests__/cli-export.test.ts +66 -0
  660. package/src/node/__tests__/cli-reset-password.test.ts +91 -0
  661. package/src/node/__tests__/cli-runtime-target.test.ts +37 -0
  662. package/src/node/__tests__/cli-site-selection.test.ts +133 -0
  663. package/src/node/__tests__/cli-site-snapshot.test.ts +636 -0
  664. package/src/node/__tests__/cli-site-token-env.test.ts +137 -0
  665. package/src/node/__tests__/runtime.test.ts +346 -0
  666. package/src/node/index.ts +18 -0
  667. package/src/node/request-handler.ts +668 -0
  668. package/src/node/runtime.ts +88 -0
  669. package/src/preset.css +73 -16
  670. package/src/routes/__tests__/compose.test.ts +77 -4
  671. package/src/routes/api/__tests__/attachments.test.ts +92 -0
  672. package/src/routes/api/__tests__/collections.test.ts +138 -24
  673. package/src/routes/api/__tests__/nav-items.test.ts +81 -20
  674. package/src/routes/api/__tests__/posts.test.ts +315 -57
  675. package/src/routes/api/__tests__/search.test.ts +25 -1
  676. package/src/routes/api/__tests__/settings.test.ts +380 -2
  677. package/src/routes/api/__tests__/upload-multipart.test.ts +216 -26
  678. package/src/routes/api/__tests__/uploads.test.ts +420 -0
  679. package/src/routes/api/attachments.ts +24 -0
  680. package/src/routes/api/collections.ts +59 -28
  681. package/src/routes/api/custom-urls.ts +6 -5
  682. package/src/routes/api/export.ts +37 -7
  683. package/src/routes/api/internal/__tests__/api-tokens.test.ts +55 -0
  684. package/src/routes/api/internal/__tests__/sites.test.ts +472 -0
  685. package/src/routes/api/internal/__tests__/uploads.test.ts +131 -0
  686. package/src/routes/api/internal/api-tokens.ts +17 -0
  687. package/src/routes/api/internal/sites.ts +241 -0
  688. package/src/routes/api/internal/uploads.ts +37 -0
  689. package/src/routes/api/nav-items.ts +24 -15
  690. package/src/routes/api/posts.ts +222 -74
  691. package/src/routes/api/search.ts +50 -9
  692. package/src/routes/api/settings.ts +141 -7
  693. package/src/routes/api/upload-multipart.ts +105 -16
  694. package/src/routes/api/upload.ts +90 -21
  695. package/src/routes/api/uploads.ts +247 -0
  696. package/src/routes/auth/__tests__/dev.test.ts +135 -0
  697. package/src/routes/auth/__tests__/hosted-sso.test.ts +136 -0
  698. package/src/routes/auth/__tests__/setup.test.ts +73 -49
  699. package/src/routes/auth/dev.ts +72 -0
  700. package/src/routes/auth/hosted-sso.ts +64 -0
  701. package/src/routes/auth/reset.tsx +41 -7
  702. package/src/routes/auth/setup.tsx +37 -38
  703. package/src/routes/auth/signin.tsx +36 -6
  704. package/src/routes/compose.tsx +25 -38
  705. package/src/routes/dash/__tests__/font-theme.test.ts +45 -34
  706. package/src/routes/dash/__tests__/settings-avatar.test.ts +24 -11
  707. package/src/routes/dash/custom-urls.tsx +245 -67
  708. package/src/routes/dash/settings.tsx +530 -139
  709. package/src/routes/feed/__tests__/rss.test.ts +200 -51
  710. package/src/routes/feed/__tests__/sitemap.test.ts +64 -0
  711. package/src/routes/feed/rss.ts +112 -48
  712. package/src/routes/feed/sitemap.ts +16 -6
  713. package/src/routes/hosted/__tests__/domain-check.test.ts +94 -0
  714. package/src/routes/hosted/domain-check.ts +39 -0
  715. package/src/routes/pages/__tests__/collections.test.ts +27 -21
  716. package/src/routes/pages/__tests__/featured.test.ts +9 -2
  717. package/src/routes/pages/archive.tsx +79 -33
  718. package/src/routes/pages/brand.tsx +119 -0
  719. package/src/routes/pages/collection.tsx +201 -65
  720. package/src/routes/pages/collections.tsx +46 -20
  721. package/src/routes/pages/featured.tsx +31 -39
  722. package/src/routes/pages/home.tsx +50 -25
  723. package/src/routes/pages/latest.tsx +20 -4
  724. package/src/routes/pages/new.tsx +10 -6
  725. package/src/routes/pages/page.tsx +27 -53
  726. package/src/routes/pages/partials.tsx +83 -0
  727. package/src/routes/pages/search.tsx +11 -9
  728. package/src/routes/pages/theme-sample.tsx +98 -0
  729. package/src/runtime/__tests__/node.test.ts +258 -0
  730. package/src/runtime/__tests__/readiness.test.ts +89 -0
  731. package/src/runtime/cloudflare.ts +102 -0
  732. package/src/runtime/index.ts +14 -0
  733. package/src/runtime/node.ts +187 -0
  734. package/src/runtime/readiness.ts +129 -0
  735. package/src/runtime/site.ts +169 -0
  736. package/src/services/__tests__/api-token.test.ts +5 -2
  737. package/src/services/__tests__/auth.test.ts +348 -0
  738. package/src/services/__tests__/collection.test.ts +199 -16
  739. package/src/services/__tests__/custom-url.test.ts +10 -3
  740. package/src/services/__tests__/hosted-handoff.test.ts +123 -0
  741. package/src/services/__tests__/media.test.ts +256 -33
  742. package/src/services/__tests__/navigation.test.ts +89 -4
  743. package/src/services/__tests__/post-timeline.test.ts +169 -2
  744. package/src/services/__tests__/post.test.ts +498 -39
  745. package/src/services/__tests__/search.test.ts +145 -14
  746. package/src/services/__tests__/settings.test.ts +162 -2
  747. package/src/services/__tests__/site-admin.test.ts +48 -0
  748. package/src/services/__tests__/site-profile.test.ts +175 -0
  749. package/src/services/api-token.ts +44 -11
  750. package/src/services/auth.ts +211 -9
  751. package/src/services/bootstrap.ts +68 -0
  752. package/src/services/collection.ts +556 -100
  753. package/src/services/custom-url.ts +42 -12
  754. package/src/services/export.ts +2269 -320
  755. package/src/services/hosted-handoff.ts +184 -0
  756. package/src/services/index.ts +110 -12
  757. package/src/services/media.ts +309 -39
  758. package/src/services/navigation.ts +141 -20
  759. package/src/services/path.ts +45 -9
  760. package/src/services/post.ts +1640 -248
  761. package/src/services/search.ts +222 -29
  762. package/src/services/settings.ts +202 -48
  763. package/src/services/site-admin.ts +825 -0
  764. package/src/services/site-member.ts +74 -0
  765. package/src/services/site-profile.ts +52 -0
  766. package/src/services/site.ts +250 -0
  767. package/src/services/upload-session.ts +707 -0
  768. package/src/style-cjk-tc.css +3 -0
  769. package/src/style-cjk.css +3 -0
  770. package/src/styles/components.css +647 -16
  771. package/src/styles/fonts/latin.css +4 -0
  772. package/src/styles/fonts/noto-serif-sc/400/02629e5d0a9860b7fe32ec1f0563213a.woff2 +0 -0
  773. package/src/styles/fonts/noto-serif-sc/400/031089da45fbfb7dc18ac827bef4c56e.woff2 +0 -0
  774. package/src/styles/fonts/noto-serif-sc/400/03ac785139320b7b13bac9c150bf72bf.woff2 +0 -0
  775. package/src/styles/fonts/noto-serif-sc/400/0b1f83a3c7e715560a55ad9eb0fb1c94.woff2 +0 -0
  776. package/src/styles/fonts/noto-serif-sc/400/14b040a2dda256936bc7b3470e548394.woff2 +0 -0
  777. package/src/styles/fonts/noto-serif-sc/400/189f272ea2600c74d576b7b15c014922.woff2 +0 -0
  778. package/src/styles/fonts/noto-serif-sc/400/1fbccc182322b513f57cd156a9a491b0.woff2 +0 -0
  779. package/src/styles/fonts/noto-serif-sc/400/21e0a71d86be7b8a9e812e7af09dd061.woff2 +0 -0
  780. package/src/styles/fonts/noto-serif-sc/400/265e048cc9f2f8a711ba585a534d5351.woff2 +0 -0
  781. package/src/styles/fonts/noto-serif-sc/400/2ae2ca951489c9d50cde5b36a2a5515b.woff2 +0 -0
  782. package/src/styles/fonts/noto-serif-sc/400/2ba802b14f21a58fc61606c88fa93373.woff2 +0 -0
  783. package/src/styles/fonts/noto-serif-sc/400/2deb444546774c3a3ab38c75eb69cdfb.woff2 +0 -0
  784. package/src/styles/fonts/noto-serif-sc/400/2fbccf9a3853eb59db1a825e044515fd.woff2 +0 -0
  785. package/src/styles/fonts/noto-serif-sc/400/2ff009fa8701505d7f3dc6c83763f019.woff2 +0 -0
  786. package/src/styles/fonts/noto-serif-sc/400/31424fe5d54692e7c8b38021ccb8597c.woff2 +0 -0
  787. package/src/styles/fonts/noto-serif-sc/400/360c190344c26278bbc50e2f4d6a2b3f.woff2 +0 -0
  788. package/src/styles/fonts/noto-serif-sc/400/39b0bbc910af9d2d6dcd8bd4abd6387d.woff2 +0 -0
  789. package/src/styles/fonts/noto-serif-sc/400/3adc8f6350cf5067bcb6dc5e44c45d41.woff2 +0 -0
  790. package/src/styles/fonts/noto-serif-sc/400/3bed5bd57de8f738e53cddaea88983d9.woff2 +0 -0
  791. package/src/styles/fonts/noto-serif-sc/400/3c201fd8d1bb20abe7d06b940e83a4d9.woff2 +0 -0
  792. package/src/styles/fonts/noto-serif-sc/400/3d385ea0880df7204258e290648ec012.woff2 +0 -0
  793. package/src/styles/fonts/noto-serif-sc/400/3dc1a4f0d7af59e16b5162a2b077a442.woff2 +0 -0
  794. package/src/styles/fonts/noto-serif-sc/400/3dc68e473fe23bd076dd46785cd23583.woff2 +0 -0
  795. package/src/styles/fonts/noto-serif-sc/400/435b7dca567809813fcb395a27ed83a0.woff2 +0 -0
  796. package/src/styles/fonts/noto-serif-sc/400/43693195e775d515689fa035394067fd.woff2 +0 -0
  797. package/src/styles/fonts/noto-serif-sc/400/43fb49e5b79ee7e553869d84e6e08b1e.woff2 +0 -0
  798. package/src/styles/fonts/noto-serif-sc/400/44dcbbc3cc8f22e613b342d691511ab6.woff2 +0 -0
  799. package/src/styles/fonts/noto-serif-sc/400/474fac21b12b7efd71f7c321578878b0.woff2 +0 -0
  800. package/src/styles/fonts/noto-serif-sc/400/4a23fe6e82fd496b5eb20401b6164efe.woff2 +0 -0
  801. package/src/styles/fonts/noto-serif-sc/400/4bc743968cf1c3ce5711de67ef1ccc4d.woff2 +0 -0
  802. package/src/styles/fonts/noto-serif-sc/400/4cf0f292f3358bd2f73b1cf4ec1476f3.woff2 +0 -0
  803. package/src/styles/fonts/noto-serif-sc/400/501f66f24bce8234441954de1b568403.woff2 +0 -0
  804. package/src/styles/fonts/noto-serif-sc/400/53a88404451448cd2e620a0ca0e45a20.woff2 +0 -0
  805. package/src/styles/fonts/noto-serif-sc/400/557cd00c5d6827e13d72a0c71b23587b.woff2 +0 -0
  806. package/src/styles/fonts/noto-serif-sc/400/563fa31542d553f25abab65cf7f81e1d.woff2 +0 -0
  807. package/src/styles/fonts/noto-serif-sc/400/56e1c4734bbbb38af2fbc262bf6e98f2.woff2 +0 -0
  808. package/src/styles/fonts/noto-serif-sc/400/5947f5da5da9a352a2b534ee64bfc29a.woff2 +0 -0
  809. package/src/styles/fonts/noto-serif-sc/400/5a10741e41259e235841440394c0763d.woff2 +0 -0
  810. package/src/styles/fonts/noto-serif-sc/400/60d8b0805a0a8c54a6cca216004beff5.woff2 +0 -0
  811. package/src/styles/fonts/noto-serif-sc/400/61bf4287453da4025d03fa6b2dba66ca.woff2 +0 -0
  812. package/src/styles/fonts/noto-serif-sc/400/638369541268ed5a10af97ad77498c73.woff2 +0 -0
  813. package/src/styles/fonts/noto-serif-sc/400/687d0f0f90a9b23e40102e16ad8e9836.woff2 +0 -0
  814. package/src/styles/fonts/noto-serif-sc/400/6e2164fad867d166de2e5b274f04a563.woff2 +0 -0
  815. package/src/styles/fonts/noto-serif-sc/400/6eefc9d430171c1e1e4034ecadee31c8.woff2 +0 -0
  816. package/src/styles/fonts/noto-serif-sc/400/71eafb8fbe3a734283517e230ad8b6db.woff2 +0 -0
  817. package/src/styles/fonts/noto-serif-sc/400/751f54dbb115140d5b645a6ba4aff5d3.woff2 +0 -0
  818. package/src/styles/fonts/noto-serif-sc/400/7609e7e74dd4d916a7abc7ecc7d95f7e.woff2 +0 -0
  819. package/src/styles/fonts/noto-serif-sc/400/76b9d6fe838ae4151d95ce7200aa2bf6.woff2 +0 -0
  820. package/src/styles/fonts/noto-serif-sc/400/78ce29fed872e44fc9014d94875d2aac.woff2 +0 -0
  821. package/src/styles/fonts/noto-serif-sc/400/79a85a253e9b3f12d2e2cb15e16b3003.woff2 +0 -0
  822. package/src/styles/fonts/noto-serif-sc/400/7caa14a095a6bc313aab780fe4ff7999.woff2 +0 -0
  823. package/src/styles/fonts/noto-serif-sc/400/7d138084cf03c14116b11297fce0e3e3.woff2 +0 -0
  824. package/src/styles/fonts/noto-serif-sc/400/880162ae92cd9e120eb4e4e11fae459d.woff2 +0 -0
  825. package/src/styles/fonts/noto-serif-sc/400/8f2b960c2823670e94f5b08aa65657e6.woff2 +0 -0
  826. package/src/styles/fonts/noto-serif-sc/400/8f89f57230d184f92a36e241874229d7.woff2 +0 -0
  827. package/src/styles/fonts/noto-serif-sc/400/9180de34b48b325200a97e267befff32.woff2 +0 -0
  828. package/src/styles/fonts/noto-serif-sc/400/95df3b9f681d9df411c30aea5b24f2e0.woff2 +0 -0
  829. package/src/styles/fonts/noto-serif-sc/400/975af5a496e8d87d821910aa9fe4d598.woff2 +0 -0
  830. package/src/styles/fonts/noto-serif-sc/400/97a874bbf55ce89a4ab7cd27c7e938b1.woff2 +0 -0
  831. package/src/styles/fonts/noto-serif-sc/400/9fd53607094e329fa8e5c785b3ff0f1a.woff2 +0 -0
  832. package/src/styles/fonts/noto-serif-sc/400/a578742770fcd2226e3c45b5b6efdcb0.woff2 +0 -0
  833. package/src/styles/fonts/noto-serif-sc/400/ae401fb4db80d5ff5cd3f8d9bc811070.woff2 +0 -0
  834. package/src/styles/fonts/noto-serif-sc/400/b0ab3a7f319ce6dd3c9a4de2674e7c72.woff2 +0 -0
  835. package/src/styles/fonts/noto-serif-sc/400/b159deb135e9946eea0572d52778170b.woff2 +0 -0
  836. package/src/styles/fonts/noto-serif-sc/400/b91450304d9ac44f5c6e0da0792e055d.woff2 +0 -0
  837. package/src/styles/fonts/noto-serif-sc/400/baa325551b381c5e035ef143e56d4abe.woff2 +0 -0
  838. package/src/styles/fonts/noto-serif-sc/400/c0c7836749e585cee24ab2f8457c5b01.woff2 +0 -0
  839. package/src/styles/fonts/noto-serif-sc/400/c2ac4ef1860812036ca2b8c4e4089bdc.woff2 +0 -0
  840. package/src/styles/fonts/noto-serif-sc/400/c31019c08bd22464f7a88f090281404c.woff2 +0 -0
  841. package/src/styles/fonts/noto-serif-sc/400/c57ee3b49b7e45b995539a6b2c51f138.woff2 +0 -0
  842. package/src/styles/fonts/noto-serif-sc/400/cd75ca47da9ae4c0899e37d4c543319b.woff2 +0 -0
  843. package/src/styles/fonts/noto-serif-sc/400/d12ce1d8445213317f9163283e58a05d.woff2 +0 -0
  844. package/src/styles/fonts/noto-serif-sc/400/d28fb13acf9ced9f0657fd4012c81cd2.woff2 +0 -0
  845. package/src/styles/fonts/noto-serif-sc/400/d3714e6b90de8e2085dfb2514464dd6a.woff2 +0 -0
  846. package/src/styles/fonts/noto-serif-sc/400/d3beff96216c8af1aa79246476b6a323.woff2 +0 -0
  847. package/src/styles/fonts/noto-serif-sc/400/d3e311f30c811dc339c262a79a51877e.woff2 +0 -0
  848. package/src/styles/fonts/noto-serif-sc/400/d51f4cdc83711e510f5d25f03235597e.woff2 +0 -0
  849. package/src/styles/fonts/noto-serif-sc/400/d5df4a8dfd4328c67d933b3912c6ad0f.woff2 +0 -0
  850. package/src/styles/fonts/noto-serif-sc/400/dccda6a2e2db3b530788bdfa2acd1979.woff2 +0 -0
  851. package/src/styles/fonts/noto-serif-sc/400/f5237486197aeff59341a1ff38b8eff8.woff2 +0 -0
  852. package/src/styles/fonts/noto-serif-sc/400/f553d54ef931066712d8f3f0ce018e1b.woff2 +0 -0
  853. package/src/styles/fonts/noto-serif-sc/400/f649cba8e14c33d6bf2265483b14b895.woff2 +0 -0
  854. package/src/styles/fonts/noto-serif-sc/400/f6839df1bf7cb4dc8d27e5ea55bbe633.woff2 +0 -0
  855. package/src/styles/fonts/noto-serif-sc/400/f6b7304e028980f77a7f7007bb540abd.woff2 +0 -0
  856. package/src/styles/fonts/noto-serif-sc/400/f8db8bef0a6e1178835d350ae0d384a1.woff2 +0 -0
  857. package/src/styles/fonts/noto-serif-sc/400/fb4649a82c50620773d79820e2e5ff13.woff2 +0 -0
  858. package/src/styles/fonts/noto-serif-sc/400/fb9402d6c6357a825affc402f14d5a7e.woff2 +0 -0
  859. package/src/styles/fonts/noto-serif-sc/700/0041f681602cc834bb5c55ced0155b8e.woff2 +0 -0
  860. package/src/styles/fonts/noto-serif-sc/700/00c9ac960d866ffaf8a866e5939024e2.woff2 +0 -0
  861. package/src/styles/fonts/noto-serif-sc/700/02e48e353415a00e0f556608cab33a43.woff2 +0 -0
  862. package/src/styles/fonts/noto-serif-sc/700/02faf6bb0ab4d56ada037c0bbaf9b9f7.woff2 +0 -0
  863. package/src/styles/fonts/noto-serif-sc/700/057a3d44d7fc606f113d863376d0ecf0.woff2 +0 -0
  864. package/src/styles/fonts/noto-serif-sc/700/10f2b44b3711d3f5bdcc30d373b543d1.woff2 +0 -0
  865. package/src/styles/fonts/noto-serif-sc/700/14c1506106d92621bafb11016735194e.woff2 +0 -0
  866. package/src/styles/fonts/noto-serif-sc/700/154a2c266902003bd8b7449386b10776.woff2 +0 -0
  867. package/src/styles/fonts/noto-serif-sc/700/19e39850472250bfdbce654d30859879.woff2 +0 -0
  868. package/src/styles/fonts/noto-serif-sc/700/1b5d0d740450fb996749464c9b882025.woff2 +0 -0
  869. package/src/styles/fonts/noto-serif-sc/700/1bca0a2a8840ad0ee9414940593db144.woff2 +0 -0
  870. package/src/styles/fonts/noto-serif-sc/700/1cda27dcaab977ae4ef5d5ab2a10ae03.woff2 +0 -0
  871. package/src/styles/fonts/noto-serif-sc/700/1f4bc38a1c50f55f335f5411cae47696.woff2 +0 -0
  872. package/src/styles/fonts/noto-serif-sc/700/2022cf097cb952d9fe75b53b4587d2c3.woff2 +0 -0
  873. package/src/styles/fonts/noto-serif-sc/700/2240a3c43ca5ef59ae3c348c7884792f.woff2 +0 -0
  874. package/src/styles/fonts/noto-serif-sc/700/280e3d2b58e9ad3501816072e01b0c13.woff2 +0 -0
  875. package/src/styles/fonts/noto-serif-sc/700/2874d07e228da9583b0e73646dacd498.woff2 +0 -0
  876. package/src/styles/fonts/noto-serif-sc/700/29d49891713a2785a3a383001cf58c59.woff2 +0 -0
  877. package/src/styles/fonts/noto-serif-sc/700/2d81eb6ab0ebbc0cabfb3a3341ba8800.woff2 +0 -0
  878. package/src/styles/fonts/noto-serif-sc/700/3179006d1c7ebfa50d27482a2859d9a0.woff2 +0 -0
  879. package/src/styles/fonts/noto-serif-sc/700/34c2edb3c37f71258f5c4a31091f0c6c.woff2 +0 -0
  880. package/src/styles/fonts/noto-serif-sc/700/389a950f2a1211946d294716e679e381.woff2 +0 -0
  881. package/src/styles/fonts/noto-serif-sc/700/42a74b6a625bbf0a9616ed4db3152c88.woff2 +0 -0
  882. package/src/styles/fonts/noto-serif-sc/700/457485e72835364662dfead6281638c1.woff2 +0 -0
  883. package/src/styles/fonts/noto-serif-sc/700/488846410760fe128dae939836ca5423.woff2 +0 -0
  884. package/src/styles/fonts/noto-serif-sc/700/48eb0a91e50c7f026e248c64145e72af.woff2 +0 -0
  885. package/src/styles/fonts/noto-serif-sc/700/4b0e79ba18b2ce424fa93e84996d7cba.woff2 +0 -0
  886. package/src/styles/fonts/noto-serif-sc/700/54e301f412730f391225db59dae1c8d5.woff2 +0 -0
  887. package/src/styles/fonts/noto-serif-sc/700/597d69d0710e0178b162afb0a0c20401.woff2 +0 -0
  888. package/src/styles/fonts/noto-serif-sc/700/5cc23a76e122d0ad2f7cede41bc35b27.woff2 +0 -0
  889. package/src/styles/fonts/noto-serif-sc/700/5d48855bed5f3554eff91b573d7376ac.woff2 +0 -0
  890. package/src/styles/fonts/noto-serif-sc/700/5ddcbe564b29ef08632e1aeb33455435.woff2 +0 -0
  891. package/src/styles/fonts/noto-serif-sc/700/605667a998e91e2b6a4a3cd7c31fe5a9.woff2 +0 -0
  892. package/src/styles/fonts/noto-serif-sc/700/67f32ceea9e78e5109f87724ad886010.woff2 +0 -0
  893. package/src/styles/fonts/noto-serif-sc/700/68f2fab82ec8e9291f08c3145111549c.woff2 +0 -0
  894. package/src/styles/fonts/noto-serif-sc/700/69519ada3f3f74ca20aacb8af48ab6b4.woff2 +0 -0
  895. package/src/styles/fonts/noto-serif-sc/700/6a6e884fb2b65ec5b4a3d5ecd0d01a6a.woff2 +0 -0
  896. package/src/styles/fonts/noto-serif-sc/700/6e1a8b45b01939088c3a8cfcf8c10681.woff2 +0 -0
  897. package/src/styles/fonts/noto-serif-sc/700/70adaf50c56d5ff859c64d35e0f1e34e.woff2 +0 -0
  898. package/src/styles/fonts/noto-serif-sc/700/72ee453ac0e19bd2c631c8921c44e3de.woff2 +0 -0
  899. package/src/styles/fonts/noto-serif-sc/700/753b5f6fb254bacb6618ace25af3df60.woff2 +0 -0
  900. package/src/styles/fonts/noto-serif-sc/700/76d4244186d118eea245d1385a4de2ec.woff2 +0 -0
  901. package/src/styles/fonts/noto-serif-sc/700/7a86b155111ba20f3e87306ff6beac77.woff2 +0 -0
  902. package/src/styles/fonts/noto-serif-sc/700/7b1e76975b0984e6f83e3f9f8069e784.woff2 +0 -0
  903. package/src/styles/fonts/noto-serif-sc/700/7b6c60131822a0e4d36d980d52509d4e.woff2 +0 -0
  904. package/src/styles/fonts/noto-serif-sc/700/7cbda564cb2dd4799ab9e89d51286aa7.woff2 +0 -0
  905. package/src/styles/fonts/noto-serif-sc/700/7f60eefa15956d6f06dd92404887d58c.woff2 +0 -0
  906. package/src/styles/fonts/noto-serif-sc/700/7f8c15e0ecb102738981d9fa4cb6b921.woff2 +0 -0
  907. package/src/styles/fonts/noto-serif-sc/700/812b5a4b87f3a7b4afc1cfebc864f413.woff2 +0 -0
  908. package/src/styles/fonts/noto-serif-sc/700/812dfb7f8144d01b3cc9d5ce0b472f40.woff2 +0 -0
  909. package/src/styles/fonts/noto-serif-sc/700/84742b1ede4f0bb6d27131298eba21b4.woff2 +0 -0
  910. package/src/styles/fonts/noto-serif-sc/700/8555f0285e3d28e95e2fc0ccccd9caff.woff2 +0 -0
  911. package/src/styles/fonts/noto-serif-sc/700/8e04e64c8f68d292a18d4160fbde8671.woff2 +0 -0
  912. package/src/styles/fonts/noto-serif-sc/700/958efb9b2fa2ea0008ffef009885f9f8.woff2 +0 -0
  913. package/src/styles/fonts/noto-serif-sc/700/9ca9b71010a5faeee7047ef97aeee13b.woff2 +0 -0
  914. package/src/styles/fonts/noto-serif-sc/700/9cd0b77920b9d6c64eb686493123fc76.woff2 +0 -0
  915. package/src/styles/fonts/noto-serif-sc/700/9ebd27835ffcbd794e67151ab046ce68.woff2 +0 -0
  916. package/src/styles/fonts/noto-serif-sc/700/a077f51cfb5cffb4ff4d8e229c0e9e79.woff2 +0 -0
  917. package/src/styles/fonts/noto-serif-sc/700/a397997b579d3945c9c70a979c17a8ad.woff2 +0 -0
  918. package/src/styles/fonts/noto-serif-sc/700/a68d9d5027803832bb28e78cdcd04949.woff2 +0 -0
  919. package/src/styles/fonts/noto-serif-sc/700/a904b05966368bcf90b632c7c2e5f76b.woff2 +0 -0
  920. package/src/styles/fonts/noto-serif-sc/700/aa218a2c45f3749537ce876201e5152b.woff2 +0 -0
  921. package/src/styles/fonts/noto-serif-sc/700/aa28db16818f9eaa8c817f289e1c3270.woff2 +0 -0
  922. package/src/styles/fonts/noto-serif-sc/700/aa96d698491c2540e2dcf7009c65c456.woff2 +0 -0
  923. package/src/styles/fonts/noto-serif-sc/700/ae25c41034ddc1a9e0b41f5034c9aa4b.woff2 +0 -0
  924. package/src/styles/fonts/noto-serif-sc/700/ae289ae3f8cdb54a3a6c07174517afec.woff2 +0 -0
  925. package/src/styles/fonts/noto-serif-sc/700/b02dfa2aa52cbdb1b2f11a9f44335469.woff2 +0 -0
  926. package/src/styles/fonts/noto-serif-sc/700/b2e326f7f9b807451bf9c745df747efe.woff2 +0 -0
  927. package/src/styles/fonts/noto-serif-sc/700/c33c59feccf391f0c5f1f5d24e36d1fe.woff2 +0 -0
  928. package/src/styles/fonts/noto-serif-sc/700/c82fd9456d7465b5e5bd3659e9b14c55.woff2 +0 -0
  929. package/src/styles/fonts/noto-serif-sc/700/c90b7b65d2b9696fbf3a506738f94d68.woff2 +0 -0
  930. package/src/styles/fonts/noto-serif-sc/700/cba6ad3981cb7861428d4be169ee8124.woff2 +0 -0
  931. package/src/styles/fonts/noto-serif-sc/700/cc26525aa2af1f0b929af32ce50a7fba.woff2 +0 -0
  932. package/src/styles/fonts/noto-serif-sc/700/cd0e7b51eddb22a77a09b025c0281434.woff2 +0 -0
  933. package/src/styles/fonts/noto-serif-sc/700/cd6d074f3957d58bac58437fc97e5e33.woff2 +0 -0
  934. package/src/styles/fonts/noto-serif-sc/700/cef249b6d013fb0cc0d574176bc23811.woff2 +0 -0
  935. package/src/styles/fonts/noto-serif-sc/700/d0bd387fda28e58d3c9b3efa2468dd8a.woff2 +0 -0
  936. package/src/styles/fonts/noto-serif-sc/700/da93ae099ff3b7aae27b3f674d3fc721.woff2 +0 -0
  937. package/src/styles/fonts/noto-serif-sc/700/e264213b9e102dabc603adb6e4fda5e6.woff2 +0 -0
  938. package/src/styles/fonts/noto-serif-sc/700/e7c7ef3669ae48c0a736f06ca471e1d7.woff2 +0 -0
  939. package/src/styles/fonts/noto-serif-sc/700/e81a742cacef744130c40de1b90837d8.woff2 +0 -0
  940. package/src/styles/fonts/noto-serif-sc/700/e8b755172122d1d0a5dd453e96b0ff24.woff2 +0 -0
  941. package/src/styles/fonts/noto-serif-sc/700/e99280299c305402eaa5271a3e36c49b.woff2 +0 -0
  942. package/src/styles/fonts/noto-serif-sc/700/e9c66b085052ece66bfadf45f711d3e1.woff2 +0 -0
  943. package/src/styles/fonts/noto-serif-sc/700/ed7c6dafaa6d8bcf015ef0ca574837df.woff2 +0 -0
  944. package/src/styles/fonts/noto-serif-sc/700/f2900a1d30c3a33129f4e2225669bd0e.woff2 +0 -0
  945. package/src/styles/fonts/noto-serif-sc/700/fa7d3b99744d7f2dc9e00864a97a62d6.woff2 +0 -0
  946. package/src/styles/fonts/noto-serif-sc/700/fb0e90665980954719c2eb685b130bc0.woff2 +0 -0
  947. package/src/styles/fonts/noto-serif-sc/noto-serif-sc.css +3615 -0
  948. package/src/styles/fonts/noto-serif-tc/400/008ea9091e332c639ceb18874eacd60c.woff2 +0 -0
  949. package/src/styles/fonts/noto-serif-tc/400/053bd3d7aec0040d0cc50c261a1f4e3e.woff2 +0 -0
  950. package/src/styles/fonts/noto-serif-tc/400/0713613227cc4c686c45a279f8bdc166.woff2 +0 -0
  951. package/src/styles/fonts/noto-serif-tc/400/0e97f44ebc65384c346fe19bcc52fa20.woff2 +0 -0
  952. package/src/styles/fonts/noto-serif-tc/400/1139d32ae2bdeb26c0c8f31330aa9a9f.woff2 +0 -0
  953. package/src/styles/fonts/noto-serif-tc/400/15f8c0df47fd639d1b0d9bd5cf507c9b.woff2 +0 -0
  954. package/src/styles/fonts/noto-serif-tc/400/1668bd859ffe15bed7d5563117d8d5fb.woff2 +0 -0
  955. package/src/styles/fonts/noto-serif-tc/400/190e3f8632494e7c095117f26b1c811e.woff2 +0 -0
  956. package/src/styles/fonts/noto-serif-tc/400/19ad151c22ce1befe0a9ea643fbee570.woff2 +0 -0
  957. package/src/styles/fonts/noto-serif-tc/400/1c820b5295868008ca7c78afa5b7655d.woff2 +0 -0
  958. package/src/styles/fonts/noto-serif-tc/400/1fbe225742c69f4ba9ea5f74922f0ca1.woff2 +0 -0
  959. package/src/styles/fonts/noto-serif-tc/400/2a7cedfcd6e4c7cec36f4fd7b0f329c2.woff2 +0 -0
  960. package/src/styles/fonts/noto-serif-tc/400/2acea04a920f6af31e7db97052f563c6.woff2 +0 -0
  961. package/src/styles/fonts/noto-serif-tc/400/2e98b666924b8e0a09d1aeeefd24bdd2.woff2 +0 -0
  962. package/src/styles/fonts/noto-serif-tc/400/2fd3fceb6faed5e3db768e88d7614dca.woff2 +0 -0
  963. package/src/styles/fonts/noto-serif-tc/400/35cf5dd04315e0b906e1a413d7905a2f.woff2 +0 -0
  964. package/src/styles/fonts/noto-serif-tc/400/387c811226f303af62f1e21aae6f5c83.woff2 +0 -0
  965. package/src/styles/fonts/noto-serif-tc/400/3b41385fc27419c19822060daa0b5cb3.woff2 +0 -0
  966. package/src/styles/fonts/noto-serif-tc/400/3cbe4a697fd595ef42c899de7d3e5445.woff2 +0 -0
  967. package/src/styles/fonts/noto-serif-tc/400/3d83dacbbec3d8532ae9afede21f3aab.woff2 +0 -0
  968. package/src/styles/fonts/noto-serif-tc/400/47479c470fae70f10b7c964a7ecbf274.woff2 +0 -0
  969. package/src/styles/fonts/noto-serif-tc/400/4dc0728df0f2ba70796f45f05654c7ba.woff2 +0 -0
  970. package/src/styles/fonts/noto-serif-tc/400/4dc2bc2c55b47f57d13b63aa6b1c8bd4.woff2 +0 -0
  971. package/src/styles/fonts/noto-serif-tc/400/4e1cc6aafb411b572c8d3511e925ecf1.woff2 +0 -0
  972. package/src/styles/fonts/noto-serif-tc/400/5227dbe9933760a48baff21ebd13fc98.woff2 +0 -0
  973. package/src/styles/fonts/noto-serif-tc/400/526b263e72c189f4b065738aaa6d423a.woff2 +0 -0
  974. package/src/styles/fonts/noto-serif-tc/400/54da934819a917f561b439bfd10f88b6.woff2 +0 -0
  975. package/src/styles/fonts/noto-serif-tc/400/5bfc7a121c35ae42623ef804fb525e0e.woff2 +0 -0
  976. package/src/styles/fonts/noto-serif-tc/400/5f90024544c2907c6c0203c6210c50be.woff2 +0 -0
  977. package/src/styles/fonts/noto-serif-tc/400/649b12d7cee7bb981842946e4547e6ca.woff2 +0 -0
  978. package/src/styles/fonts/noto-serif-tc/400/653bef2ed891ae48d8ed712283080649.woff2 +0 -0
  979. package/src/styles/fonts/noto-serif-tc/400/67d2a81f06ba352f17fbdc3a5e6ea59e.woff2 +0 -0
  980. package/src/styles/fonts/noto-serif-tc/400/68304f3229cf763465f044fccb5892c0.woff2 +0 -0
  981. package/src/styles/fonts/noto-serif-tc/400/688a88911e4da17b609196a959b8b930.woff2 +0 -0
  982. package/src/styles/fonts/noto-serif-tc/400/6db6ddf72c38a78ce44c1327701152e1.woff2 +0 -0
  983. package/src/styles/fonts/noto-serif-tc/400/77a7533bd21ccd33192d142a93555aa8.woff2 +0 -0
  984. package/src/styles/fonts/noto-serif-tc/400/7d65a3d6a65050eb5e6eca43398aeba4.woff2 +0 -0
  985. package/src/styles/fonts/noto-serif-tc/400/7dfc711962c8771f97e7c8898a6bcb65.woff2 +0 -0
  986. package/src/styles/fonts/noto-serif-tc/400/7ef123b62d530fcba73974fa265e0aae.woff2 +0 -0
  987. package/src/styles/fonts/noto-serif-tc/400/80466082a896fd328f30a78593c7c568.woff2 +0 -0
  988. package/src/styles/fonts/noto-serif-tc/400/8a3c84b0df36f851f5fea75ee8757951.woff2 +0 -0
  989. package/src/styles/fonts/noto-serif-tc/400/8b0c8c9f8cfa9fa090d97c5a5efb1f4c.woff2 +0 -0
  990. package/src/styles/fonts/noto-serif-tc/400/8dc035a34c76e6515ca203e2df182588.woff2 +0 -0
  991. package/src/styles/fonts/noto-serif-tc/400/8eb06109812cb80be44f47b8179c2709.woff2 +0 -0
  992. package/src/styles/fonts/noto-serif-tc/400/904324af375d5fd370af1054355a050e.woff2 +0 -0
  993. package/src/styles/fonts/noto-serif-tc/400/911a2092d64d6d6494b254d819af2b91.woff2 +0 -0
  994. package/src/styles/fonts/noto-serif-tc/400/9de02d745b8e25c6411fb152fb067748.woff2 +0 -0
  995. package/src/styles/fonts/noto-serif-tc/400/9eb33a430058d839ebbe2af4b2e0daa9.woff2 +0 -0
  996. package/src/styles/fonts/noto-serif-tc/400/9f5a73aa8ba417688019d628f334db07.woff2 +0 -0
  997. package/src/styles/fonts/noto-serif-tc/400/a0f0c06d5c7a3ffa97706178cce212a8.woff2 +0 -0
  998. package/src/styles/fonts/noto-serif-tc/400/a38c1830367f784181b6f544b0b11bbd.woff2 +0 -0
  999. package/src/styles/fonts/noto-serif-tc/400/a9cf85e27428c14351d30eac8cbc8d91.woff2 +0 -0
  1000. package/src/styles/fonts/noto-serif-tc/400/aa0ce6740f301351761a0615cc8b2e99.woff2 +0 -0
  1001. package/src/styles/fonts/noto-serif-tc/400/bc3f0cb8b55ee11d32b94ca488976f8d.woff2 +0 -0
  1002. package/src/styles/fonts/noto-serif-tc/400/bcb3307527d6d0033bf0f17660b91e71.woff2 +0 -0
  1003. package/src/styles/fonts/noto-serif-tc/400/bf1acc86e17b4229c548828a9d6f455d.woff2 +0 -0
  1004. package/src/styles/fonts/noto-serif-tc/400/c3fbc1f2557c343863a10698f8c966a2.woff2 +0 -0
  1005. package/src/styles/fonts/noto-serif-tc/400/c5c1c0be944ea39a3f50a02d32f5b759.woff2 +0 -0
  1006. package/src/styles/fonts/noto-serif-tc/400/c5f1075caf6d1344ee720de85114a521.woff2 +0 -0
  1007. package/src/styles/fonts/noto-serif-tc/400/cae29b3f8951eaf20d2f61c2206e28d9.woff2 +0 -0
  1008. package/src/styles/fonts/noto-serif-tc/400/d043b8d7a48bb0ac59ee1f1477d88eee.woff2 +0 -0
  1009. package/src/styles/fonts/noto-serif-tc/400/d320b000b5978c7251148a6a154741b8.woff2 +0 -0
  1010. package/src/styles/fonts/noto-serif-tc/400/da13b136efb1d1e4c76575af8f79a698.woff2 +0 -0
  1011. package/src/styles/fonts/noto-serif-tc/400/da2cf0ec56bf69374ee37764c7e3ea3d.woff2 +0 -0
  1012. package/src/styles/fonts/noto-serif-tc/400/daf62255dd60679946f28c442ca62533.woff2 +0 -0
  1013. package/src/styles/fonts/noto-serif-tc/400/dd01a1035345f6921a48525b8ce08f06.woff2 +0 -0
  1014. package/src/styles/fonts/noto-serif-tc/400/e4fb59479cedc87ba79785590bf861ca.woff2 +0 -0
  1015. package/src/styles/fonts/noto-serif-tc/400/e5d00355f73293d40b61299459d17ca5.woff2 +0 -0
  1016. package/src/styles/fonts/noto-serif-tc/400/e647b8d2efc501c0cc0e407249cc7135.woff2 +0 -0
  1017. package/src/styles/fonts/noto-serif-tc/400/e6e60ffb2ebd1828628764b507060aea.woff2 +0 -0
  1018. package/src/styles/fonts/noto-serif-tc/400/f00eb499abb94fa7b799d6d8c9b050e9.woff2 +0 -0
  1019. package/src/styles/fonts/noto-serif-tc/400/f7d36ffff7a75c9c6216d576a57dd00d.woff2 +0 -0
  1020. package/src/styles/fonts/noto-serif-tc/400/f7f3f63e7a149cd89eccab3b52171d05.woff2 +0 -0
  1021. package/src/styles/fonts/noto-serif-tc/400/fcc41f6a067ddd658bba5c9dff234a32.woff2 +0 -0
  1022. package/src/styles/fonts/noto-serif-tc/400/fd6ad889fcf3583bd9b0b6db53aad434.woff2 +0 -0
  1023. package/src/styles/fonts/noto-serif-tc/700/0c055db157e7a13f3103cc2a6b67fec3.woff2 +0 -0
  1024. package/src/styles/fonts/noto-serif-tc/700/0d3f5cc265cb6c439c517f2c4cebbddf.woff2 +0 -0
  1025. package/src/styles/fonts/noto-serif-tc/700/1259e5825b314fe2b8bb96d6e8069ee5.woff2 +0 -0
  1026. package/src/styles/fonts/noto-serif-tc/700/12c518ebfe62818af550c08947e359e7.woff2 +0 -0
  1027. package/src/styles/fonts/noto-serif-tc/700/145831a59caa06d894022fe60212ed21.woff2 +0 -0
  1028. package/src/styles/fonts/noto-serif-tc/700/169a096e61d38a773216f51d1ec2cc06.woff2 +0 -0
  1029. package/src/styles/fonts/noto-serif-tc/700/1884a2b22d314c7d57707f03aec348e0.woff2 +0 -0
  1030. package/src/styles/fonts/noto-serif-tc/700/1e2640116bbba817f43c43cc69371cf1.woff2 +0 -0
  1031. package/src/styles/fonts/noto-serif-tc/700/2573703213da30d3ba18925b100b2c2b.woff2 +0 -0
  1032. package/src/styles/fonts/noto-serif-tc/700/26839c0e47c73514b8d8f660d24d6b19.woff2 +0 -0
  1033. package/src/styles/fonts/noto-serif-tc/700/2a22e14a9ad53f2abb3c7e85017b7d12.woff2 +0 -0
  1034. package/src/styles/fonts/noto-serif-tc/700/2b3e8c5703b91f39f6027f43f0da6f4b.woff2 +0 -0
  1035. package/src/styles/fonts/noto-serif-tc/700/2f27ee4fb2cf6a280e110e09c18ef73e.woff2 +0 -0
  1036. package/src/styles/fonts/noto-serif-tc/700/31342cebfa5ea7fac06b4ea372d96bc5.woff2 +0 -0
  1037. package/src/styles/fonts/noto-serif-tc/700/375329ba0b50b94b35006498e555867c.woff2 +0 -0
  1038. package/src/styles/fonts/noto-serif-tc/700/427577dcb707d1d35eebd155b4222aa7.woff2 +0 -0
  1039. package/src/styles/fonts/noto-serif-tc/700/450a5b53be0a8a778bb0b623e86b652f.woff2 +0 -0
  1040. package/src/styles/fonts/noto-serif-tc/700/477866c8396474a17317dcac3e7a014f.woff2 +0 -0
  1041. package/src/styles/fonts/noto-serif-tc/700/478ebdaadda7775c391c5dcab4e697df.woff2 +0 -0
  1042. package/src/styles/fonts/noto-serif-tc/700/48d6a97a185c799be4fe67aaf7edf213.woff2 +0 -0
  1043. package/src/styles/fonts/noto-serif-tc/700/490edb9fc8a4356aea556eed32287464.woff2 +0 -0
  1044. package/src/styles/fonts/noto-serif-tc/700/4c4bdd0b3f3a52e28f3b643c1c5d43be.woff2 +0 -0
  1045. package/src/styles/fonts/noto-serif-tc/700/4c96411f3693a9a8657a9c1190f82bce.woff2 +0 -0
  1046. package/src/styles/fonts/noto-serif-tc/700/4c9aa12aba2a6a57410eacaff7427916.woff2 +0 -0
  1047. package/src/styles/fonts/noto-serif-tc/700/4cca7233bf8ce5dec2e5d146b993d626.woff2 +0 -0
  1048. package/src/styles/fonts/noto-serif-tc/700/4d0a9128d06ea857f203bf5d007b1ab9.woff2 +0 -0
  1049. package/src/styles/fonts/noto-serif-tc/700/4e5384920bbb155d9d8d74887b09ea5b.woff2 +0 -0
  1050. package/src/styles/fonts/noto-serif-tc/700/50cfd672bfa62512ba090420acf35c87.woff2 +0 -0
  1051. package/src/styles/fonts/noto-serif-tc/700/551b1d7a0b80c8d42af09863cdca7f01.woff2 +0 -0
  1052. package/src/styles/fonts/noto-serif-tc/700/555d990ab3fd7d3d66c6d1fa9a82fec5.woff2 +0 -0
  1053. package/src/styles/fonts/noto-serif-tc/700/5979c33a7eb5963bf8e83e46931b5fb5.woff2 +0 -0
  1054. package/src/styles/fonts/noto-serif-tc/700/59966ee0b069b577510fe68c350da0ee.woff2 +0 -0
  1055. package/src/styles/fonts/noto-serif-tc/700/60a14064ed334f0155795d795e926abe.woff2 +0 -0
  1056. package/src/styles/fonts/noto-serif-tc/700/611b62d5fd9698d9b5ce495ba6f14c93.woff2 +0 -0
  1057. package/src/styles/fonts/noto-serif-tc/700/6e83fe0b6e708eaf1c3003d6dee11488.woff2 +0 -0
  1058. package/src/styles/fonts/noto-serif-tc/700/70861376e5d4f92f8aa7aa1b2749b617.woff2 +0 -0
  1059. package/src/styles/fonts/noto-serif-tc/700/7124d150570d39ced8d45507dc11ca1e.woff2 +0 -0
  1060. package/src/styles/fonts/noto-serif-tc/700/79a7fdf7d9c722b5723ae25e6ff8e203.woff2 +0 -0
  1061. package/src/styles/fonts/noto-serif-tc/700/8c8393bc875f1ee36697a2113f4421ea.woff2 +0 -0
  1062. package/src/styles/fonts/noto-serif-tc/700/8e6c9bb43afb8cbbff7cf1055e67c9bd.woff2 +0 -0
  1063. package/src/styles/fonts/noto-serif-tc/700/90ac4f9d2aa02afdace2843b49fc18bb.woff2 +0 -0
  1064. package/src/styles/fonts/noto-serif-tc/700/90b6f57d77847f512fd11db74fa912f1.woff2 +0 -0
  1065. package/src/styles/fonts/noto-serif-tc/700/913759e6690f9fc0746a20b96f4bdcb4.woff2 +0 -0
  1066. package/src/styles/fonts/noto-serif-tc/700/9154e26efe532a85a27d80902f5a2d6c.woff2 +0 -0
  1067. package/src/styles/fonts/noto-serif-tc/700/94e7ed67f1557b76fead6b6e456a0415.woff2 +0 -0
  1068. package/src/styles/fonts/noto-serif-tc/700/95127a92346c04fec1fa81d6295b0a28.woff2 +0 -0
  1069. package/src/styles/fonts/noto-serif-tc/700/9fbc06b2e3ff16b9d705c76db563ef17.woff2 +0 -0
  1070. package/src/styles/fonts/noto-serif-tc/700/a3b929542e6c5a0644b73a7c8a8b6c03.woff2 +0 -0
  1071. package/src/styles/fonts/noto-serif-tc/700/a8857f5d478f101c053ba02d2f223e90.woff2 +0 -0
  1072. package/src/styles/fonts/noto-serif-tc/700/aa64c9953af43ca65832f413895bb433.woff2 +0 -0
  1073. package/src/styles/fonts/noto-serif-tc/700/ada8f0241244c60ec8d3d59ad37f20a5.woff2 +0 -0
  1074. package/src/styles/fonts/noto-serif-tc/700/b341de0bc0bfe194a6c28dcfb566029e.woff2 +0 -0
  1075. package/src/styles/fonts/noto-serif-tc/700/b846c293981ca5429eabaa967f222f26.woff2 +0 -0
  1076. package/src/styles/fonts/noto-serif-tc/700/be64f9379412876e00fd3a0bfa6b6fe9.woff2 +0 -0
  1077. package/src/styles/fonts/noto-serif-tc/700/befed8a4fa817773fa7109db6fe07f56.woff2 +0 -0
  1078. package/src/styles/fonts/noto-serif-tc/700/c09ee2b219982f8d46ad9968b7e6e0ba.woff2 +0 -0
  1079. package/src/styles/fonts/noto-serif-tc/700/c39ec937c6a8d124e8b68cf829ea5ad4.woff2 +0 -0
  1080. package/src/styles/fonts/noto-serif-tc/700/c3fd21315345ae541f6e98067059fa19.woff2 +0 -0
  1081. package/src/styles/fonts/noto-serif-tc/700/c568a16e3168ceb1f191b70022c492ea.woff2 +0 -0
  1082. package/src/styles/fonts/noto-serif-tc/700/c5e66d60be3375835bbd8d6b797f6eac.woff2 +0 -0
  1083. package/src/styles/fonts/noto-serif-tc/700/cd10a3af2133805d8c92104d1ee6ff18.woff2 +0 -0
  1084. package/src/styles/fonts/noto-serif-tc/700/d15a3317942b7d31978a759fbf2222c8.woff2 +0 -0
  1085. package/src/styles/fonts/noto-serif-tc/700/d740dc2e854aaa7b3dcdd3ed25455eeb.woff2 +0 -0
  1086. package/src/styles/fonts/noto-serif-tc/700/d8325ba7ae651bc30440905bd67b95f1.woff2 +0 -0
  1087. package/src/styles/fonts/noto-serif-tc/700/e2204cf85edcb96c5de5c3dcf240da9d.woff2 +0 -0
  1088. package/src/styles/fonts/noto-serif-tc/700/e3e913e145ddcd9323b2a0972967feb6.woff2 +0 -0
  1089. package/src/styles/fonts/noto-serif-tc/700/f2fb1f1fbf7e44afb53c672ec286a22e.woff2 +0 -0
  1090. package/src/styles/fonts/noto-serif-tc/700/f372129c60aaece937cf7b91ee75c9b8.woff2 +0 -0
  1091. package/src/styles/fonts/noto-serif-tc/700/f5d7487963d43c89da63aaf10f2e6fb7.woff2 +0 -0
  1092. package/src/styles/fonts/noto-serif-tc/700/f75496953a40ff241178240209f56990.woff2 +0 -0
  1093. package/src/styles/fonts/noto-serif-tc/700/f92d74d1d217d21b39075ff23f79f7fd.woff2 +0 -0
  1094. package/src/styles/fonts/noto-serif-tc/700/f9d6d981d8b87b3e469027277f585741.woff2 +0 -0
  1095. package/src/styles/fonts/noto-serif-tc/700/fa8ed469ef290bfeb571418fe0abb628.woff2 +0 -0
  1096. package/src/styles/fonts/noto-serif-tc/700/fb61b690208eff56e6d8432951270901.woff2 +0 -0
  1097. package/src/styles/fonts/noto-serif-tc/700/ff0937ad63cda71ff420945ead55ab4d.woff2 +0 -0
  1098. package/src/styles/fonts/noto-serif-tc/noto-serif-tc.css +3104 -0
  1099. package/src/styles/tokens.css +42 -7
  1100. package/src/styles/ui.css +7597 -3395
  1101. package/src/types/app-context.ts +7 -0
  1102. package/src/types/bindings.ts +77 -27
  1103. package/src/types/config.ts +104 -4
  1104. package/src/types/constants.ts +43 -4
  1105. package/src/types/entities.ts +63 -4
  1106. package/src/types/operations.ts +45 -15
  1107. package/src/types/props.ts +48 -10
  1108. package/src/types/views.ts +51 -8
  1109. package/src/ui/__tests__/color-themes.test.ts +81 -0
  1110. package/src/ui/__tests__/font-themes.test.ts +76 -7
  1111. package/src/ui/color-themes.ts +424 -238
  1112. package/src/ui/compose/ComposeDialog.tsx +149 -91
  1113. package/src/ui/compose/ComposePrompt.tsx +20 -3
  1114. package/src/ui/dash/ActionButtons.tsx +16 -2
  1115. package/src/ui/dash/DangerZone.tsx +10 -1
  1116. package/src/ui/dash/StatusBadge.tsx +3 -3
  1117. package/src/ui/dash/appearance/AdvancedContent.tsx +9 -2
  1118. package/src/ui/dash/appearance/ColorThemeContent.tsx +326 -63
  1119. package/src/ui/dash/appearance/FontThemeContent.tsx +75 -17
  1120. package/src/ui/dash/appearance/NavigationContent.tsx +48 -21
  1121. package/src/ui/dash/settings/AccountContent.tsx +7 -2
  1122. package/src/ui/dash/settings/AccountMenuContent.tsx +242 -111
  1123. package/src/ui/dash/settings/ApiTokensContent.tsx +38 -8
  1124. package/src/ui/dash/settings/AvatarContent.tsx +1 -1
  1125. package/src/ui/dash/settings/DeleteAccountContent.tsx +300 -0
  1126. package/src/ui/dash/settings/GeneralContent.tsx +120 -6
  1127. package/src/ui/dash/settings/SessionsContent.tsx +23 -5
  1128. package/src/ui/dash/settings/SettingsDirectory.tsx +99 -0
  1129. package/src/ui/dash/settings/SettingsRootContent.tsx +176 -221
  1130. package/src/ui/feed/CuratedThreadPreview.tsx +73 -0
  1131. package/src/ui/feed/LinkCard.tsx +52 -36
  1132. package/src/ui/feed/NoteCard.tsx +10 -4
  1133. package/src/ui/feed/PostStatusBadges.tsx +4 -21
  1134. package/src/ui/feed/QuoteCard.tsx +11 -3
  1135. package/src/ui/feed/ThreadPreview.tsx +11 -5
  1136. package/src/ui/feed/TimelineFeed.tsx +53 -15
  1137. package/src/ui/feed/__tests__/timeline-cards.test.ts +172 -0
  1138. package/src/ui/feed/thread-preview-state.ts +2 -4
  1139. package/src/ui/font-themes.ts +211 -43
  1140. package/src/ui/layouts/BaseLayout.tsx +161 -30
  1141. package/src/ui/layouts/SiteLayout.tsx +51 -18
  1142. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +169 -0
  1143. package/src/ui/pages/ArchivePage.tsx +320 -120
  1144. package/src/ui/pages/BrandPage.tsx +927 -0
  1145. package/src/ui/pages/CollectionEditorPage.tsx +155 -0
  1146. package/src/ui/pages/CollectionPage.tsx +482 -23
  1147. package/src/ui/pages/CollectionsPage.tsx +57 -57
  1148. package/src/ui/pages/FeaturedPage.tsx +24 -3
  1149. package/src/ui/pages/HomePage.tsx +13 -0
  1150. package/src/ui/pages/PostPage.tsx +13 -4
  1151. package/src/ui/pages/SearchPage.tsx +12 -2
  1152. package/src/ui/pages/ThemeSamplePage.tsx +1160 -0
  1153. package/src/ui/shared/AdminBreadcrumb.tsx +22 -6
  1154. package/src/ui/shared/CollectionDirectory.tsx +131 -0
  1155. package/src/ui/shared/CollectionsManager.tsx +277 -0
  1156. package/src/ui/shared/DecorativeQuoteMark.tsx +32 -0
  1157. package/src/ui/shared/HomePageBranding.tsx +22 -0
  1158. package/src/ui/shared/JantBrandMark.tsx +35 -0
  1159. package/src/ui/shared/MediaGallery.tsx +106 -36
  1160. package/src/ui/shared/PaginatedPageHeader.tsx +49 -0
  1161. package/src/ui/shared/Pagination.tsx +2 -6
  1162. package/src/ui/shared/PostFooter.tsx +130 -67
  1163. package/src/ui/shared/__tests__/home-page-branding.test.tsx +14 -0
  1164. package/src/ui/shared/__tests__/media-gallery.test.ts +82 -0
  1165. package/src/ui/shared/__tests__/navigation-labels.test.ts +103 -0
  1166. package/src/ui/shared/__tests__/pagination.test.ts +18 -1
  1167. package/src/ui/shared/__tests__/post-footer.test.ts +122 -0
  1168. package/src/ui/shared/collection-management-labels.ts +161 -0
  1169. package/src/ui/shared/navigation-labels.ts +126 -0
  1170. package/src/vendor/datastar.js +1955 -7
  1171. package/dist/client/assets/heic-to-DIRPI3VF.js +0 -1
  1172. package/dist/client/assets/module-RjUF93sV.js +0 -716
  1173. package/dist/client/assets/native-48B9X9Wg.js +0 -1
  1174. package/dist/client/assets/url-FWFqPJPb.js +0 -1
  1175. package/dist/client/client.css +0 -1
  1176. package/dist/client/client.js +0 -33565
  1177. package/src/client/components/collection-sidebar-types.ts +0 -43
  1178. package/src/client/tiptap/paste-image.ts +0 -129
  1179. package/src/lib/emoji-catalog.ts +0 -963
  1180. package/src/lib/icon-catalog.ts +0 -5213
  1181. package/src/ui/shared/CollectionsSidebar.tsx +0 -294
@@ -10,6 +10,7 @@
10
10
  import { LitElement, html, nothing } from "lit";
11
11
  import { classMap } from "lit/directives/class-map.js";
12
12
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
13
+ import { unsafeSVG } from "lit/directives/unsafe-svg.js";
13
14
  import type { Editor, JSONContent } from "@tiptap/core";
14
15
  import type {
15
16
  ComposeFormat,
@@ -17,25 +18,70 @@ import type {
17
18
  ComposeLabels,
18
19
  ComposeCollection,
19
20
  ComposeSubmitDetail,
21
+ ComposeSubmitAttachment,
20
22
  ComposeAttachment,
21
23
  DraftItem,
22
24
  LocalDraft,
25
+ ComposeFullscreenOpenDetail,
26
+ ComposeFullscreenReplyContext,
27
+ ComposeFullscreenCloseDetail,
23
28
  } from "./compose-types.js";
24
29
  import type { CollectionSubmitDetail } from "./collection-types.js";
25
30
  import { showToast } from "../toast.js";
31
+ import { publicPath } from "../runtime-paths.js";
26
32
  import type { JantComposeEditor } from "./jant-compose-editor.js";
27
33
  import { getMediaCategory } from "../../lib/upload.js";
34
+ import { getSlugValidationIssue } from "../../lib/slug-format.js";
28
35
  import { createTiptapEditor } from "../tiptap/create-editor.js";
29
- import { renderCollectionIcon } from "../../lib/icons.js";
30
36
 
31
37
  interface ReplyToData {
32
38
  contentHtml: string;
33
39
  dateText: string;
34
40
  }
35
41
 
42
+ interface ComposeMediaAttachmentResponse {
43
+ id: string;
44
+ previewUrl: string;
45
+ alt?: string;
46
+ mimeType: string;
47
+ url?: string;
48
+ summary?: string;
49
+ }
50
+
51
+ interface ComposePostResponse {
52
+ id: string;
53
+ threadId?: string;
54
+ format: ComposeFormat;
55
+ slug?: string | null;
56
+ visibility?: ComposeVisibility | null;
57
+ replyToId?: string | null;
58
+ collectionIds?: string[];
59
+ mediaAttachments?: ComposeMediaAttachmentResponse[];
60
+ title?: string | null;
61
+ body?: string | null;
62
+ url?: string | null;
63
+ sourceName?: string | null;
64
+ sourceUrl?: string | null;
65
+ quoteText?: string | null;
66
+ rating?: number | null;
67
+ publishedAt?: number | null;
68
+ bodyHtml?: string | null;
69
+ }
70
+
71
+ interface DraftsResponse {
72
+ posts?: Record<string, unknown>[];
73
+ }
74
+
75
+ interface ComposeOpenOptions {
76
+ collectionId?: string;
77
+ restoreDraft?: boolean;
78
+ }
79
+
36
80
  interface ComposeStateSnapshot {
37
81
  format: ComposeFormat;
38
82
  collectionIds: string[];
83
+ slug: string;
84
+ visibility: ComposeVisibility;
39
85
  title: string;
40
86
  bodyJson: JSONContent | null;
41
87
  url: string;
@@ -64,7 +110,192 @@ interface ComposeStateSnapshot {
64
110
  attachmentOrder: string[];
65
111
  }
66
112
 
113
+ const EDITOR_FLOATING_UI_SELECTOR = "[data-editor-floating-ui]";
114
+
115
+ interface ComposeFilePickerCloseDetail {
116
+ cancelled?: boolean;
117
+ }
118
+
119
+ const COMPOSE_DIALOG_HEADER_ICONS = {
120
+ drafts: `
121
+ <rect x="3.85" y="3.45" width="7.85" height="8.35" rx="2.35" />
122
+ <rect
123
+ x="6.15"
124
+ y="5.75"
125
+ width="7.85"
126
+ height="8.35"
127
+ rx="2.35"
128
+ fill="var(--compose-dialog-icon-paper-fill)"
129
+ stroke="none"
130
+ />
131
+ <rect x="6.15" y="5.75" width="7.85" height="8.35" rx="2.35" />
132
+ <path d="M8.55 8.55h3.2" stroke-width="1.2" />
133
+ <path d="M8.55 10.8h3.95" stroke-width="1.2" />
134
+ <path d="M8.55 13.05h2.45" stroke-width="1.2" />
135
+ `,
136
+ } as const;
137
+
138
+ const COMPOSE_PUBLISH_VISIBILITY_ICONS: Record<ComposeVisibility, string> = {
139
+ public: `
140
+ <circle cx="8" cy="8" r="5.15" />
141
+ <path d="M3.85 8h8.3" />
142
+ <path d="M8 2.85c1.22 1.32 1.95 3.08 1.95 5.15S9.22 11.83 8 13.15" />
143
+ <path d="M8 2.85C6.78 4.17 6.05 5.93 6.05 8S6.78 11.83 8 13.15" />
144
+ `,
145
+ latest_hidden: `
146
+ <path
147
+ d="M2.55 8c1.38-2.18 3.44-3.4 5.45-3.4S12.07 5.82 13.45 8c-1.38 2.18-3.44 3.4-5.45 3.4S3.93 10.18 2.55 8Z"
148
+ />
149
+ <path d="M4.35 11.1 11.65 4.9" />
150
+ `,
151
+ private: `
152
+ <rect x="4.05" y="7.05" width="7.9" height="5.4" rx="1.75" />
153
+ <path d="M5.95 7.05V5.9A2.05 2.05 0 0 1 8 3.85a2.05 2.05 0 0 1 2.05 2.05v1.15" />
154
+ `,
155
+ };
156
+
157
+ const COMPOSE_PUBLISH_ACTION_ICONS = {
158
+ check: `
159
+ <path d="M4.35 8.2 6.9 10.7 11.65 5.95" />
160
+ `,
161
+ chevron: `
162
+ <path d="M5.1 6.45 8 9.3l2.9-2.85" />
163
+ `,
164
+ } as const;
165
+
166
+ const COMPOSE_COLLECTION_PICKER_ICONS = {
167
+ collection: `
168
+ <rect x="3" y="5.05" width="10" height="8.15" rx="2.2" />
169
+ <path d="M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" />
170
+ `,
171
+ search: `
172
+ <circle cx="7.1" cy="7.1" r="3.65" />
173
+ <path d="m10.1 10.1 2.45 2.45" />
174
+ `,
175
+ plus: `
176
+ <path d="M8 3.5v9" />
177
+ <path d="M3.5 8h9" />
178
+ `,
179
+ plusCircle: `
180
+ <circle cx="8" cy="8" r="5.7" />
181
+ <path d="M8 5.55v4.9" />
182
+ <path d="M5.55 8h4.9" />
183
+ `,
184
+ chevron: `
185
+ <path d="M5.1 6.45 8 9.3l2.9-2.85" />
186
+ `,
187
+ } as const;
188
+
189
+ function renderComposeHeaderIcon(
190
+ icon: (typeof COMPOSE_DIALOG_HEADER_ICONS)[keyof typeof COMPOSE_DIALOG_HEADER_ICONS],
191
+ ) {
192
+ return html`<svg
193
+ class="compose-dialog-header-icon"
194
+ width="24"
195
+ height="24"
196
+ viewBox="0 0 18 18"
197
+ fill="none"
198
+ stroke="currentColor"
199
+ stroke-width="1.25"
200
+ stroke-linecap="round"
201
+ stroke-linejoin="round"
202
+ aria-hidden="true"
203
+ >
204
+ ${unsafeSVG(icon)}
205
+ </svg>`;
206
+ }
207
+
208
+ function renderComposePublishVisibilityIcon(
209
+ icon: string,
210
+ classes: string | ReturnType<typeof classMap>,
211
+ ) {
212
+ return html`<span class=${classes} aria-hidden="true">
213
+ <svg
214
+ class="compose-publish-visibility-svg"
215
+ width="16"
216
+ height="16"
217
+ viewBox="0 0 16 16"
218
+ fill="none"
219
+ stroke="currentColor"
220
+ stroke-width="1.35"
221
+ stroke-linecap="round"
222
+ stroke-linejoin="round"
223
+ >
224
+ ${unsafeSVG(icon)}
225
+ </svg>
226
+ </span>`;
227
+ }
228
+
229
+ function renderComposeCollectionPickerIcon(
230
+ icon: (typeof COMPOSE_COLLECTION_PICKER_ICONS)[keyof typeof COMPOSE_COLLECTION_PICKER_ICONS],
231
+ classes: string,
232
+ ) {
233
+ return html`<svg
234
+ class=${classes}
235
+ width="16"
236
+ height="16"
237
+ viewBox="0 0 16 16"
238
+ fill="none"
239
+ stroke="currentColor"
240
+ stroke-width="1.35"
241
+ stroke-linecap="round"
242
+ stroke-linejoin="round"
243
+ aria-hidden="true"
244
+ >
245
+ ${unsafeSVG(icon)}
246
+ </svg>`;
247
+ }
248
+
249
+ function renderComposePublishActionIcon(
250
+ icon: (typeof COMPOSE_PUBLISH_ACTION_ICONS)[keyof typeof COMPOSE_PUBLISH_ACTION_ICONS],
251
+ classes: string,
252
+ ) {
253
+ return html`<svg
254
+ class=${classes}
255
+ width="14"
256
+ height="14"
257
+ viewBox="0 0 16 16"
258
+ fill="none"
259
+ stroke="currentColor"
260
+ stroke-width="1.55"
261
+ stroke-linecap="round"
262
+ stroke-linejoin="round"
263
+ aria-hidden="true"
264
+ >
265
+ ${unsafeSVG(icon)}
266
+ </svg>`;
267
+ }
268
+
269
+ function toComposeCollections(value: unknown): ComposeCollection[] {
270
+ if (!Array.isArray(value)) return [];
271
+
272
+ return value.flatMap((item) => {
273
+ if (!item || typeof item !== "object") return [];
274
+
275
+ const id = Reflect.get(item, "id");
276
+ const title = Reflect.get(item, "title");
277
+ if (typeof id !== "string" || typeof title !== "string") return [];
278
+
279
+ return [{ id, title }];
280
+ });
281
+ }
282
+
283
+ function isEmptyComposeDoc(json: JSONContent): boolean {
284
+ if (!json.content || json.content.length === 0) return true;
285
+ return json.content.every(
286
+ (node) =>
287
+ node.type === "paragraph" && (!node.content || node.content.length === 0),
288
+ );
289
+ }
290
+
291
+ function normalizeComposeDoc(json: JSONContent | null): JSONContent | null {
292
+ if (!json) return null;
293
+ return isEmptyComposeDoc(json) ? null : json;
294
+ }
295
+
67
296
  export class JantComposeDialog extends LitElement {
297
+ private static _lastNewPostVisibility: ComposeVisibility = "public";
298
+
68
299
  static properties = {
69
300
  collections: { type: Array },
70
301
  labels: { type: Object },
@@ -77,7 +308,6 @@ export class JantComposeDialog extends LitElement {
77
308
  _loading: { state: true },
78
309
  _collectionIds: { state: true },
79
310
  _showCollection: { state: true },
80
- _showMoreMenu: { state: true },
81
311
  _collectionSearch: { state: true },
82
312
  _altPanelOpen: { state: true },
83
313
  _altPanelIndex: { state: true },
@@ -95,9 +325,14 @@ export class JantComposeDialog extends LitElement {
95
325
  _replyToId: { state: true },
96
326
  _replyToData: { state: true },
97
327
  _replyExpanded: { state: true },
328
+ _slug: { state: true },
98
329
  _visibility: { state: true },
99
- _featured: { state: true },
100
- _showVisibilityMenu: { state: true },
330
+ _showPublishPanel: { state: true },
331
+ _suggestedSlug: { state: true },
332
+ _suggestedSlugLoading: { state: true },
333
+ _slugCheckLoading: { state: true },
334
+ _slugTaken: { state: true },
335
+ _visibilityLocked: { state: true },
101
336
  };
102
337
 
103
338
  declare collections: ComposeCollection[];
@@ -111,7 +346,6 @@ export class JantComposeDialog extends LitElement {
111
346
  declare _loading: boolean;
112
347
  declare _collectionIds: string[];
113
348
  declare _showCollection: boolean;
114
- declare _showMoreMenu: boolean;
115
349
  declare _collectionSearch: string;
116
350
  declare _altPanelOpen: boolean;
117
351
  declare _altPanelIndex: number;
@@ -129,19 +363,40 @@ export class JantComposeDialog extends LitElement {
129
363
  declare _replyToId: string | null;
130
364
  declare _replyToData: ReplyToData | null;
131
365
  declare _replyExpanded: boolean;
366
+ declare _slug: string;
132
367
  declare _visibility: ComposeVisibility;
133
- declare _featured: boolean;
134
- declare _showVisibilityMenu: boolean;
368
+ declare _showPublishPanel: boolean;
369
+ declare _suggestedSlug: string;
370
+ declare _suggestedSlugLoading: boolean;
371
+ declare _slugCheckLoading: boolean;
372
+ declare _slugTaken: boolean;
373
+ declare _visibilityLocked: boolean;
135
374
 
136
375
  private _attachedEditor: Editor | null = null;
137
376
  private _attachedTextSnapshot: JSONContent | null = null;
138
377
  private _confirmForDrafts = false;
378
+ private _confirmForAttachedText = false;
139
379
  private _draftSaveTimer: ReturnType<typeof setTimeout> | null = null;
140
380
  private _draftRestored = false;
141
381
  private _initialSnapshot: string | null = null;
142
382
  private _pageFocusApplied = false;
143
383
  private _pageLeaveRequested = false;
384
+ private _replyThreadRootId: string | null = null;
385
+ private _replyRefreshKind:
386
+ | "timeline-item"
387
+ | "post-card"
388
+ | "post-view"
389
+ | null = null;
390
+ private _replyRefreshId: string | null = null;
391
+ private _slugCheckTimer: ReturnType<typeof setTimeout> | null = null;
392
+ private _slugSuggestTimer: ReturnType<typeof setTimeout> | null = null;
393
+ private _slugSuggestRequestId = 0;
394
+ private _slugCheckRequestId = 0;
395
+ private _slugSuggestionKey = "";
144
396
  private _suppressBeforeUnload = false;
397
+ private _dialogEl: HTMLDialogElement | null = null;
398
+ private _filePickerActive = false;
399
+ private _ignoreNextEscapeClose = false;
145
400
 
146
401
  createRenderRoot() {
147
402
  this.innerHTML = "";
@@ -161,7 +416,6 @@ export class JantComposeDialog extends LitElement {
161
416
  this._loading = false;
162
417
  this._collectionIds = [];
163
418
  this._showCollection = false;
164
- this._showMoreMenu = false;
165
419
  this._collectionSearch = "";
166
420
  this._altPanelOpen = false;
167
421
  this._altPanelIndex = 0;
@@ -179,9 +433,17 @@ export class JantComposeDialog extends LitElement {
179
433
  this._replyToId = null;
180
434
  this._replyToData = null;
181
435
  this._replyExpanded = false;
182
- this._visibility = "public";
183
- this._featured = false;
184
- this._showVisibilityMenu = false;
436
+ this._replyThreadRootId = null;
437
+ this._replyRefreshKind = null;
438
+ this._replyRefreshId = null;
439
+ this._slug = "";
440
+ this._visibility = JantComposeDialog._lastNewPostVisibility;
441
+ this._showPublishPanel = false;
442
+ this._suggestedSlug = "";
443
+ this._suggestedSlugLoading = false;
444
+ this._slugCheckLoading = false;
445
+ this._slugTaken = false;
446
+ this._visibilityLocked = false;
185
447
  }
186
448
 
187
449
  private get _editor(): JantComposeEditor | null {
@@ -193,7 +455,24 @@ export class JantComposeDialog extends LitElement {
193
455
  if (this._initialSnapshot === null && this._editor) {
194
456
  this._captureInitialSnapshot();
195
457
  }
196
- if (changed.has("_format") || changed.has("_collectionIds")) {
458
+ if (
459
+ changed.has("_addCollectionPanelOpen") &&
460
+ this._addCollectionPanelOpen
461
+ ) {
462
+ this.updateComplete.then(() => {
463
+ const titleInput = this.querySelector<HTMLInputElement>(
464
+ "[data-collection-quick-dialog] [data-collection-title-input]",
465
+ );
466
+ titleInput?.focus();
467
+ titleInput?.select();
468
+ });
469
+ }
470
+ if (
471
+ changed.has("_format") ||
472
+ changed.has("_collectionIds") ||
473
+ changed.has("_slug") ||
474
+ changed.has("_visibility")
475
+ ) {
197
476
  // Schedule draft auto-save for new-post mode only
198
477
  if (!this._editPostId && !this._draftSourceId) {
199
478
  this._scheduleDraftSave();
@@ -207,7 +486,6 @@ export class JantComposeDialog extends LitElement {
207
486
  this._loading = false;
208
487
  this._collectionIds = [];
209
488
  this._showCollection = false;
210
- this._showMoreMenu = false;
211
489
  this._collectionSearch = "";
212
490
  this._altPanelOpen = false;
213
491
  this._altPanelIndex = 0;
@@ -225,28 +503,70 @@ export class JantComposeDialog extends LitElement {
225
503
  this._replyToId = null;
226
504
  this._replyToData = null;
227
505
  this._replyExpanded = false;
228
- this._visibility = "public";
229
- this._featured = false;
230
- this._showVisibilityMenu = false;
506
+ this._replyThreadRootId = null;
507
+ this._replyRefreshKind = null;
508
+ this._replyRefreshId = null;
509
+ this._slug = "";
510
+ this._visibility = JantComposeDialog._lastNewPostVisibility;
511
+ this._showPublishPanel = false;
512
+ this._suggestedSlug = "";
513
+ this._suggestedSlugLoading = false;
514
+ this._slugCheckLoading = false;
515
+ this._slugTaken = false;
516
+ this._slugSuggestionKey = "";
517
+ this._visibilityLocked = false;
231
518
  this._confirmForDrafts = false;
519
+ this._confirmForAttachedText = false;
232
520
  this._initialSnapshot = null;
233
521
  this._pageFocusApplied = false;
234
522
  this._pageLeaveRequested = false;
523
+ this._slugSuggestRequestId += 1;
524
+ this._slugCheckRequestId += 1;
235
525
  this._suppressBeforeUnload = false;
526
+ this._filePickerActive = false;
527
+ this._ignoreNextEscapeClose = false;
528
+ this._cancelSlugTimers();
236
529
  this._destroyAttachedEditor();
237
530
  this._editor?.reset();
238
531
  this._captureInitialSnapshot();
239
532
  }
240
533
 
534
+ async refreshCollections(): Promise<boolean> {
535
+ try {
536
+ const res = await fetch("/api/collections?view=compose", {
537
+ headers: { Accept: "application/json" },
538
+ });
539
+ if (!res.ok) return false;
540
+
541
+ const body = (await res.json().catch(() => null)) as {
542
+ collections?: unknown;
543
+ } | null;
544
+ if (!Array.isArray(body?.collections)) return false;
545
+
546
+ this.collections = toComposeCollections(body.collections);
547
+ return true;
548
+ } catch {
549
+ return false;
550
+ }
551
+ }
552
+
241
553
  async openEdit(id: string) {
242
554
  this.reset();
243
555
 
244
556
  const res = await fetch(`/api/posts/${id}`);
245
557
  if (!res.ok) return;
246
- const post = await res.json();
558
+ const post = (await res.json()) as ComposePostResponse;
247
559
 
248
560
  this._editPostId = id;
249
561
  this._format = post.format;
562
+ this._slug = post.slug ?? "";
563
+ this._slugTaken = false;
564
+ this._slugCheckLoading = false;
565
+ this._suggestedSlug = "";
566
+ this._suggestedSlugLoading = false;
567
+ this._slugSuggestionKey = "";
568
+ this._visibility = post.visibility ?? "public";
569
+ this._visibilityLocked = Boolean(post.replyToId);
250
570
 
251
571
  // Pre-fill collection memberships if present
252
572
  if (post.collectionIds?.length) {
@@ -268,7 +588,7 @@ export class JantComposeDialog extends LitElement {
268
588
  // Fetch text content for TipTap text media items (stored as { json, html } envelope)
269
589
  const textAttachments = await Promise.all(
270
590
  textMedia.map(
271
- async (m: { id: string; url: string; summary?: string }) => {
591
+ async (m: { id: string; url?: string; summary?: string }) => {
272
592
  try {
273
593
  const textRes = await fetch(`/api/media/${m.id}/content`);
274
594
  if (textRes.ok) {
@@ -299,12 +619,15 @@ export class JantComposeDialog extends LitElement {
299
619
 
300
620
  this._editor?.populate({
301
621
  format: post.format,
302
- title: post.title ?? undefined,
622
+ title: post.format === "quote" ? undefined : (post.title ?? undefined),
303
623
  bodyJson: post.body ?? undefined,
304
- url: post.url ?? undefined,
624
+ url:
625
+ post.format === "quote"
626
+ ? (post.sourceUrl ?? undefined)
627
+ : (post.url ?? undefined),
305
628
  quoteText: post.quoteText ?? undefined,
306
629
  quoteAuthor:
307
- post.format === "quote" ? (post.title ?? undefined) : undefined,
630
+ post.format === "quote" ? (post.sourceName ?? undefined) : undefined,
308
631
  rating: post.rating ?? undefined,
309
632
  media: nonTextMedia.map(
310
633
  (m: {
@@ -330,15 +653,48 @@ export class JantComposeDialog extends LitElement {
330
653
  });
331
654
  }
332
655
 
656
+ async openNew(options?: ComposeOpenOptions) {
657
+ this.reset();
658
+
659
+ if (options?.restoreDraft !== false) {
660
+ await this.restoreLocalDraft();
661
+ }
662
+
663
+ if (
664
+ options?.collectionId &&
665
+ !this._collectionIds.includes(options.collectionId)
666
+ ) {
667
+ this._collectionIds = [options.collectionId, ...this._collectionIds];
668
+ }
669
+
670
+ this.closest("dialog")?.showModal();
671
+ await this.updateComplete;
672
+ this._editor?.focusInput();
673
+ this._captureInitialSnapshot();
674
+ }
675
+
333
676
  /**
334
677
  * Open compose dialog in reply mode.
335
678
  *
336
- * @param id - UUID of the post being replied to
679
+ * @param id - TypeID of the post being replied to
337
680
  * @param replyData - Pre-captured content from the DOM (avoids API fetch)
681
+ * @param threadRootId - TypeID of the thread root (used for in-place timeline refresh)
682
+ * @param refreshTarget - Current view to patch after publishing the reply
338
683
  */
339
- async openReply(id: string, replyData?: ReplyToData) {
684
+ async openReply(
685
+ id: string,
686
+ replyData?: ReplyToData,
687
+ threadRootId?: string,
688
+ refreshTarget?: {
689
+ kind: "timeline-item" | "post-card" | "post-view";
690
+ id: string;
691
+ },
692
+ ) {
340
693
  this.reset();
341
694
  this._replyToId = id;
695
+ this._replyThreadRootId = threadRootId ?? id;
696
+ this._replyRefreshKind = refreshTarget?.kind ?? null;
697
+ this._replyRefreshId = refreshTarget?.id ?? null;
342
698
  this._replyToData = replyData ?? null;
343
699
  this._format = "note";
344
700
 
@@ -356,7 +712,10 @@ export class JantComposeDialog extends LitElement {
356
712
  try {
357
713
  const res = await fetch(`/api/posts/${replyToId}`);
358
714
  if (!res.ok) return;
359
- const post = await res.json();
715
+ const post = (await res.json()) as ComposePostResponse;
716
+ this._replyThreadRootId = (post.replyToId as string | null)
717
+ ? (post.threadId as string)
718
+ : (post.id as string);
360
719
  const dateText = post.publishedAt
361
720
  ? new Date(post.publishedAt * 1000).toLocaleDateString(undefined, {
362
721
  month: "short",
@@ -380,12 +739,14 @@ export class JantComposeDialog extends LitElement {
380
739
  const dialog = this.closest("dialog");
381
740
  if (dialog) {
382
741
  dialog.close();
742
+ // Prevent browsers from leaving the opener in a lingering :focus-visible state.
743
+ (document.activeElement as HTMLElement)?.blur();
383
744
  return;
384
745
  }
385
746
 
386
747
  if (this.pageMode) {
387
748
  this._suppressBeforeUnload = true;
388
- globalThis.location.assign(this.closeHref || "/");
749
+ globalThis.location.assign(this.closeHref || publicPath("/"));
389
750
  }
390
751
  }
391
752
 
@@ -404,6 +765,19 @@ export class JantComposeDialog extends LitElement {
404
765
  this._suppressBeforeUnload = true;
405
766
  }
406
767
 
768
+ private _clearFilePickerEscapeState() {
769
+ this._filePickerActive = false;
770
+ this._ignoreNextEscapeClose = false;
771
+ }
772
+
773
+ private _shouldIgnoreEscapeClose(): boolean {
774
+ if (this._filePickerActive || this._ignoreNextEscapeClose) {
775
+ this._clearFilePickerEscapeState();
776
+ return true;
777
+ }
778
+ return false;
779
+ }
780
+
407
781
  private _hasContent(): boolean {
408
782
  const editor = this._editor;
409
783
  if (!editor) return false;
@@ -414,7 +788,7 @@ export class JantComposeDialog extends LitElement {
414
788
  if (data.url.trim()) return true;
415
789
  if (data.quoteText.trim()) return true;
416
790
  if (data.quoteAuthor.trim()) return true;
417
- if (data.attachedTexts.some((t) => t.bodyJson !== null)) return true;
791
+ if (data.attachedTexts.length > 0) return true;
418
792
  if (data.rating > 0) return true;
419
793
  if (data.attachments.length > 0) return true;
420
794
  // Collection selection alone isn't content — it's metadata that
@@ -427,17 +801,27 @@ export class JantComposeDialog extends LitElement {
427
801
  const editor = this._editor;
428
802
  if (!editor) return null;
429
803
 
804
+ const editorData = editor.getData();
805
+ const attachedTexts = editor.getEffectiveAttachedTexts();
806
+ const showTitle =
807
+ this._format === "note" && editorData.title.trim().length > 0
808
+ ? editor._showTitle
809
+ : false;
810
+ const showRating = editorData.rating > 0 ? editor._showRating : false;
811
+
430
812
  return {
431
813
  format: this._format,
432
814
  collectionIds: [...this._collectionIds],
433
- title: editor._title,
434
- bodyJson: editor._bodyJson,
435
- url: editor._url,
436
- quoteText: editor._quoteText,
437
- quoteAuthor: editor._quoteAuthor,
438
- rating: editor._rating,
439
- showTitle: editor._showTitle,
440
- showRating: editor._showRating,
815
+ slug: this._slug,
816
+ visibility: this._visibility,
817
+ title: editorData.title,
818
+ bodyJson: editor.getNormalizedBodyJson(),
819
+ url: editorData.url,
820
+ quoteText: editorData.quoteText,
821
+ quoteAuthor: editorData.quoteAuthor,
822
+ rating: editorData.rating,
823
+ showTitle,
824
+ showRating,
441
825
  attachments: editor._attachments.map((attachment) => ({
442
826
  clientId: attachment.clientId,
443
827
  mediaId: attachment.mediaId,
@@ -448,14 +832,14 @@ export class JantComposeDialog extends LitElement {
448
832
  summary: attachment.summary,
449
833
  chars: attachment.chars,
450
834
  })),
451
- attachedTexts: editor._attachedTexts.map((item) => ({
835
+ attachedTexts: attachedTexts.map((item) => ({
452
836
  clientId: item.clientId,
453
837
  mediaId: item.mediaId ?? null,
454
838
  bodyJson: item.bodyJson,
455
839
  bodyHtml: item.bodyHtml,
456
840
  summary: item.summary,
457
841
  })),
458
- attachmentOrder: [...editor._attachmentOrder],
842
+ attachmentOrder: editor.getEffectiveAttachmentOrder(),
459
843
  };
460
844
  }
461
845
 
@@ -473,6 +857,9 @@ export class JantComposeDialog extends LitElement {
473
857
  private _hasUnsavedChanges(): boolean {
474
858
  const currentSnapshot = this._serializeSnapshot(this._buildSnapshot());
475
859
  if (currentSnapshot === null) return false;
860
+ if (!this._editPostId && !this._draftSourceId && !this._hasContent()) {
861
+ return false;
862
+ }
476
863
  if (this._initialSnapshot === null) return this._hasContent();
477
864
  return currentSnapshot !== this._initialSnapshot;
478
865
  }
@@ -485,18 +872,23 @@ export class JantComposeDialog extends LitElement {
485
872
  this._showCollection = false;
486
873
  this._collectionSearch = "";
487
874
  }
488
- if (this._showMoreMenu) {
489
- this._showMoreMenu = false;
490
- }
491
- if (this._showVisibilityMenu) {
492
- this._showVisibilityMenu = false;
875
+ if (this._showPublishPanel) {
876
+ this._showPublishPanel = false;
493
877
  }
494
878
 
495
879
  if (this._confirmPanelOpen) {
880
+ const restoreAttachedFocus = this._confirmForAttachedText;
496
881
  this._confirmPanelOpen = false;
497
882
  this._confirmForDrafts = false;
883
+ this._confirmForAttachedText = false;
498
884
  this._pageLeaveRequested = false;
499
- this.updateComplete.then(() => this._editor?.focusInput());
885
+ this.updateComplete.then(() => {
886
+ if (restoreAttachedFocus) {
887
+ this._attachedEditor?.commands.focus();
888
+ return;
889
+ }
890
+ this._editor?.focusInput();
891
+ });
500
892
  return;
501
893
  }
502
894
 
@@ -504,6 +896,7 @@ export class JantComposeDialog extends LitElement {
504
896
  if (this._editPostId) {
505
897
  if (this._hasUnsavedChanges()) {
506
898
  this._confirmForDrafts = false;
899
+ this._confirmForAttachedText = false;
507
900
  this._confirmPanelOpen = true;
508
901
  } else {
509
902
  this._closeDialog();
@@ -514,6 +907,7 @@ export class JantComposeDialog extends LitElement {
514
907
 
515
908
  if (this._hasContent()) {
516
909
  this._confirmForDrafts = false;
910
+ this._confirmForAttachedText = false;
517
911
  this._confirmPanelOpen = true;
518
912
  } else {
519
913
  this._closeDialog();
@@ -529,13 +923,24 @@ export class JantComposeDialog extends LitElement {
529
923
  }
530
924
  this._clearDraftFromStorage();
531
925
  this._confirmPanelOpen = false;
926
+ this._confirmForAttachedText = false;
532
927
  this._closeDialog();
533
- (document.activeElement as HTMLElement)?.blur();
534
928
  this.reset();
535
929
  }
536
930
 
931
+ private _discardAttachedPanel() {
932
+ this._confirmForAttachedText = false;
933
+ this._destroyAttachedEditor();
934
+ this._attachedPanelOpen = false;
935
+ this._editor?.closeAttachedPanel(this._attachedTextIndex);
936
+ }
937
+
537
938
  private _handleConfirmSave() {
538
- if (this._confirmForDrafts) {
939
+ if (this._confirmForAttachedText) {
940
+ this._confirmPanelOpen = false;
941
+ this._confirmForAttachedText = false;
942
+ this._doneAttachedPanel();
943
+ } else if (this._confirmForDrafts) {
539
944
  this._dispatchSubmit("draft");
540
945
  this._confirmPanelOpen = false;
541
946
  this.reset();
@@ -551,7 +956,10 @@ export class JantComposeDialog extends LitElement {
551
956
  }
552
957
 
553
958
  private _handleConfirmDiscard() {
554
- if (this._confirmForDrafts) {
959
+ if (this._confirmForAttachedText) {
960
+ this._confirmPanelOpen = false;
961
+ this._discardAttachedPanel();
962
+ } else if (this._confirmForDrafts) {
555
963
  if (this._draftSourceId) {
556
964
  const id = this._draftSourceId;
557
965
  fetch(`/api/posts/${id}`, { method: "DELETE" }).catch(() => {});
@@ -572,27 +980,40 @@ export class JantComposeDialog extends LitElement {
572
980
  if (!editor) return null;
573
981
 
574
982
  const editorData = editor.getData();
575
- const attachments = editorData.attachments ?? [];
576
-
577
- // Collect mediaIds from completed uploads
578
- const mediaIds = attachments
579
- .filter((a) => a.status === "done" && a.mediaId)
580
- .map((a) => a.mediaId as string);
581
-
582
- // Collect alt text keyed by mediaId
583
- const mediaAlts: Record<string, string> = {};
584
- for (const a of attachments) {
585
- if (a.mediaId && a.alt) {
586
- mediaAlts[a.mediaId] = a.alt;
983
+ const mediaAttachments = new Map(
984
+ (editorData.attachments ?? []).map((attachment) => [
985
+ attachment.clientId,
986
+ attachment,
987
+ ]),
988
+ );
989
+ const textAttachments = new Map(
990
+ editorData.attachedTexts.map((item) => [item.clientId, item]),
991
+ );
992
+ const orderedAttachments: ComposeSubmitAttachment[] = [];
993
+ for (const clientId of editorData.attachmentOrder) {
994
+ const mediaAttachment = mediaAttachments.get(clientId);
995
+ if (mediaAttachment) {
996
+ orderedAttachments.push({
997
+ type: "media",
998
+ clientId,
999
+ mediaId: mediaAttachment.mediaId,
1000
+ alt: mediaAttachment.alt || undefined,
1001
+ });
1002
+ continue;
587
1003
  }
588
- }
589
1004
 
590
- // Capture clientId → mediaId for all done attachments now,
591
- // because the editor will be reset before the deferred handler runs
592
- const mediaClientMap: Record<string, string> = {};
593
- for (const a of attachments) {
594
- if (a.mediaId) {
595
- mediaClientMap[a.clientId] = a.mediaId;
1005
+ const textAttachment = textAttachments.get(clientId);
1006
+ if (textAttachment?.bodyJson) {
1007
+ orderedAttachments.push({
1008
+ type: "text",
1009
+ clientId,
1010
+ bodyJson: textAttachment.bodyJson,
1011
+ summary: textAttachment.summary,
1012
+ mediaId: textAttachment.mediaId,
1013
+ originalBodyJson: normalizeComposeDoc(
1014
+ textAttachment.originalBodyJson ?? null,
1015
+ ),
1016
+ });
596
1017
  }
597
1018
  }
598
1019
 
@@ -603,25 +1024,56 @@ export class JantComposeDialog extends LitElement {
603
1024
  url: editorData.url,
604
1025
  quoteText: editorData.quoteText,
605
1026
  quoteAuthor: editorData.quoteAuthor,
1027
+ slug: this._slug.trim() || undefined,
606
1028
  status,
607
- visibility: this._visibility,
608
- featured: this._featured || undefined,
1029
+ visibility: this._visibilityLocked ? undefined : this._visibility,
609
1030
  rating: editorData.rating,
610
1031
  collectionIds: [...this._collectionIds],
611
- mediaIds,
612
- mediaAlts,
613
- attachedTexts: editorData.attachedTexts,
614
- attachmentOrder: editorData.attachmentOrder ?? [],
615
- mediaClientMap,
1032
+ attachments: orderedAttachments,
616
1033
  editPostId: this._editPostId ?? this._draftSourceId ?? undefined,
617
1034
  replyToId: this._replyToId ?? undefined,
1035
+ replyThreadRootId: this._replyThreadRootId ?? undefined,
1036
+ replyRefreshKind: this._replyRefreshKind ?? undefined,
1037
+ replyRefreshId: this._replyRefreshId ?? undefined,
618
1038
  };
619
1039
  }
620
1040
 
1041
+ private _focusBlockedSubmitField(): boolean {
1042
+ if (this._getSlugValidationMessage()) {
1043
+ this._revealSlugField();
1044
+ return true;
1045
+ }
1046
+
1047
+ const editor = this._editor;
1048
+ if (!editor) return false;
1049
+
1050
+ if (editor.getUrlValidationMessage()) {
1051
+ editor.revealUrlValidation();
1052
+ editor.focusUrlInput("end");
1053
+ return true;
1054
+ }
1055
+
1056
+ if (editor.getLinkTitleValidationMessage()) {
1057
+ editor.revealLinkTitleValidation();
1058
+ editor.focusLinkTitleInput("end");
1059
+ return true;
1060
+ }
1061
+
1062
+ if (this._format === "quote" && !editor._quoteText.trim()) {
1063
+ editor.focusInput("end");
1064
+ return true;
1065
+ }
1066
+
1067
+ return false;
1068
+ }
1069
+
621
1070
  private _dispatchSubmit(status: "published" | "draft"): boolean {
622
1071
  if (this._loading) return false;
623
1072
  const editor = this._editor;
624
1073
  if (!editor) return false;
1074
+ if (this._focusBlockedSubmitField()) {
1075
+ return false;
1076
+ }
625
1077
 
626
1078
  const detail = this._buildSubmitDetail(status);
627
1079
  if (!detail) return false;
@@ -644,6 +1096,7 @@ export class JantComposeDialog extends LitElement {
644
1096
  }
645
1097
 
646
1098
  private _submit(status: "published" | "draft") {
1099
+ this._showPublishPanel = false;
647
1100
  this._clearDraftFromStorage();
648
1101
  if (!this._dispatchSubmit(status)) return;
649
1102
  if (this.pageMode) {
@@ -651,8 +1104,6 @@ export class JantComposeDialog extends LitElement {
651
1104
  return;
652
1105
  }
653
1106
  this._closeDialog();
654
- // Prevent browser from restoring focus to the trigger button
655
- (document.activeElement as HTMLElement)?.blur();
656
1107
  this.reset();
657
1108
  }
658
1109
 
@@ -674,6 +1125,189 @@ export class JantComposeDialog extends LitElement {
674
1125
  .replace("%count%", String(ids.length - 1));
675
1126
  }
676
1127
 
1128
+ private _cancelSlugTimers() {
1129
+ if (this._slugCheckTimer !== null) {
1130
+ clearTimeout(this._slugCheckTimer);
1131
+ this._slugCheckTimer = null;
1132
+ }
1133
+ if (this._slugSuggestTimer !== null) {
1134
+ clearTimeout(this._slugSuggestTimer);
1135
+ this._slugSuggestTimer = null;
1136
+ }
1137
+ }
1138
+
1139
+ private _currentSlugOwnerId(): string | undefined {
1140
+ return this._editPostId ?? this._draftSourceId ?? undefined;
1141
+ }
1142
+
1143
+ private _hasManualSlug(): boolean {
1144
+ return this._slug.trim().length > 0;
1145
+ }
1146
+
1147
+ private _currentSuggestionTitle(): string {
1148
+ return this._editor?.getData().title.trim() ?? "";
1149
+ }
1150
+
1151
+ private _canSuggestSlug(): boolean {
1152
+ return this._currentSuggestionTitle().length > 0;
1153
+ }
1154
+
1155
+ private _currentSuggestionKey(): string {
1156
+ return `${this._currentSlugOwnerId() ?? ""}::${this._currentSuggestionTitle()}`;
1157
+ }
1158
+
1159
+ private _scheduleSuggestedSlugRefresh(immediate = false) {
1160
+ if (!this._showPublishPanel || this._hasManualSlug()) return;
1161
+ if (!this._canSuggestSlug()) {
1162
+ this._slugSuggestRequestId += 1;
1163
+ this._suggestedSlug = "";
1164
+ this._suggestedSlugLoading = false;
1165
+ this._slugSuggestionKey = "";
1166
+ return;
1167
+ }
1168
+
1169
+ const key = this._currentSuggestionKey();
1170
+ if (this._slugSuggestionKey !== key) {
1171
+ this._suggestedSlug = "";
1172
+ }
1173
+ if (
1174
+ !immediate &&
1175
+ !this._suggestedSlugLoading &&
1176
+ this._suggestedSlug &&
1177
+ this._slugSuggestionKey === key
1178
+ ) {
1179
+ return;
1180
+ }
1181
+
1182
+ if (this._slugSuggestTimer !== null) {
1183
+ clearTimeout(this._slugSuggestTimer);
1184
+ this._slugSuggestTimer = null;
1185
+ }
1186
+
1187
+ const run = () => void this._refreshSuggestedSlug(key);
1188
+ if (immediate) {
1189
+ run();
1190
+ return;
1191
+ }
1192
+ this._slugSuggestTimer = setTimeout(run, 250);
1193
+ }
1194
+
1195
+ private async _refreshSuggestedSlug(key: string) {
1196
+ this._slugSuggestTimer = null;
1197
+ if (this._hasManualSlug() || !this._canSuggestSlug()) return;
1198
+
1199
+ const requestId = ++this._slugSuggestRequestId;
1200
+ this._suggestedSlugLoading = true;
1201
+
1202
+ const params = new URLSearchParams({ mode: "suggest" });
1203
+ const title = this._currentSuggestionTitle();
1204
+ if (title) params.set("title", title);
1205
+ const postId = this._currentSlugOwnerId();
1206
+ if (postId) params.set("postId", postId);
1207
+
1208
+ try {
1209
+ const res = await fetch(`/api/posts/slug?${params.toString()}`);
1210
+ if (!res.ok) return;
1211
+ const json = (await res.json()) as { slug?: string };
1212
+ if (
1213
+ requestId !== this._slugSuggestRequestId ||
1214
+ this._hasManualSlug() ||
1215
+ !this._showPublishPanel
1216
+ ) {
1217
+ return;
1218
+ }
1219
+ this._suggestedSlug = json.slug?.trim() ?? "";
1220
+ this._slugSuggestionKey = key;
1221
+ } catch {
1222
+ // Suggestion is a convenience only — publish still works without it.
1223
+ } finally {
1224
+ if (requestId === this._slugSuggestRequestId) {
1225
+ this._suggestedSlugLoading = false;
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ private _scheduleSlugAvailabilityCheck() {
1231
+ if (!this._hasManualSlug()) {
1232
+ this._slugTaken = false;
1233
+ this._slugCheckLoading = false;
1234
+ return;
1235
+ }
1236
+
1237
+ if (this._slugCheckTimer !== null) {
1238
+ clearTimeout(this._slugCheckTimer);
1239
+ this._slugCheckTimer = null;
1240
+ }
1241
+
1242
+ const syncError = this._getSlugSyncValidationMessage();
1243
+ if (syncError) {
1244
+ this._slugTaken = false;
1245
+ this._slugCheckLoading = false;
1246
+ return;
1247
+ }
1248
+
1249
+ this._slugCheckLoading = true;
1250
+ const slug = this._slug.trim();
1251
+ this._slugCheckTimer = setTimeout(() => {
1252
+ void this._checkSlugAvailability(slug);
1253
+ }, 250);
1254
+ }
1255
+
1256
+ private async _checkSlugAvailability(slug: string) {
1257
+ this._slugCheckTimer = null;
1258
+ const requestId = ++this._slugCheckRequestId;
1259
+
1260
+ const params = new URLSearchParams({
1261
+ mode: "check",
1262
+ slug,
1263
+ });
1264
+ const postId = this._currentSlugOwnerId();
1265
+ if (postId) params.set("postId", postId);
1266
+
1267
+ try {
1268
+ const res = await fetch(`/api/posts/slug?${params.toString()}`);
1269
+ if (!res.ok) return;
1270
+ const json = (await res.json()) as { available?: boolean };
1271
+ if (
1272
+ requestId !== this._slugCheckRequestId ||
1273
+ this._slug.trim() !== slug
1274
+ ) {
1275
+ return;
1276
+ }
1277
+ this._slugTaken = json.available === false;
1278
+ } catch {
1279
+ // Server-side create/update remains the final authority.
1280
+ } finally {
1281
+ if (requestId === this._slugCheckRequestId) {
1282
+ this._slugCheckLoading = false;
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ private _useSuggestedSlug() {
1288
+ if (!this._suggestedSlug) return;
1289
+ this._slug = this._suggestedSlug;
1290
+ this._slugTaken = false;
1291
+ this._slugCheckLoading = false;
1292
+ this.updateComplete.then(() => {
1293
+ this.querySelector<HTMLInputElement>(
1294
+ ".compose-publish-slug-input",
1295
+ )?.focus();
1296
+ });
1297
+ }
1298
+
1299
+ private _resetCustomSlug() {
1300
+ this._slug = "";
1301
+ this._slugTaken = false;
1302
+ this._slugCheckLoading = false;
1303
+ this._scheduleSuggestedSlugRefresh(true);
1304
+ this.updateComplete.then(() => {
1305
+ this.querySelector<HTMLInputElement>(
1306
+ ".compose-publish-slug-input",
1307
+ )?.focus();
1308
+ });
1309
+ }
1310
+
677
1311
  connectedCallback() {
678
1312
  super.connectedCallback();
679
1313
  this.addEventListener("keydown", this._handleKeydown);
@@ -687,6 +1321,16 @@ export class JantComposeDialog extends LitElement {
687
1321
  "jant:compose-content-changed",
688
1322
  this._onContentChanged,
689
1323
  );
1324
+ this.addEventListener("jant:file-picker-open", this._handleFilePickerOpen);
1325
+ this.addEventListener(
1326
+ "jant:file-picker-close",
1327
+ this._handleFilePickerClose as EventListener,
1328
+ );
1329
+ this.addEventListener(
1330
+ "jant:fullscreen-open",
1331
+ this._handleFullscreenOpen as EventListener,
1332
+ );
1333
+ this.addEventListener("pointerdown", this._handlePointerDown);
690
1334
  // Listen on document — fullscreen element lives on document.body, outside the dialog
691
1335
  document.addEventListener(
692
1336
  "jant:fullscreen-close",
@@ -697,9 +1341,10 @@ export class JantComposeDialog extends LitElement {
697
1341
  window.addEventListener("beforeunload", this._onBeforeUnload);
698
1342
 
699
1343
  // Intercept native dialog cancel (ESC) to route through requestClose
700
- const dialog = this.closest("dialog");
701
- if (dialog) {
702
- dialog.addEventListener("cancel", this._handleDialogCancel);
1344
+ this._dialogEl = this.closest("dialog");
1345
+ if (this._dialogEl) {
1346
+ this._dialogEl.addEventListener("cancel", this._handleDialogCancel);
1347
+ this._dialogEl.addEventListener("click", this._handleDialogClick);
703
1348
  }
704
1349
 
705
1350
  if (this.pageMode) {
@@ -720,53 +1365,157 @@ export class JantComposeDialog extends LitElement {
720
1365
  "jant:compose-content-changed",
721
1366
  this._onContentChanged,
722
1367
  );
1368
+ this.removeEventListener(
1369
+ "jant:file-picker-open",
1370
+ this._handleFilePickerOpen,
1371
+ );
1372
+ this.removeEventListener(
1373
+ "jant:file-picker-close",
1374
+ this._handleFilePickerClose as EventListener,
1375
+ );
1376
+ this.removeEventListener(
1377
+ "jant:fullscreen-open",
1378
+ this._handleFullscreenOpen as EventListener,
1379
+ );
1380
+ this.removeEventListener("pointerdown", this._handlePointerDown);
723
1381
  document.removeEventListener(
724
1382
  "jant:fullscreen-close",
725
1383
  this._handleFullscreenClose as EventListener,
726
1384
  );
727
1385
  window.removeEventListener("beforeunload", this._onBeforeUnload);
1386
+ this._cancelSlugTimers();
728
1387
  this._destroyAttachedEditor();
729
1388
  this._cancelDraftSaveTimer();
730
1389
 
731
- const dialog = this.closest("dialog");
732
- if (dialog) {
733
- dialog.removeEventListener("cancel", this._handleDialogCancel);
1390
+ if (this._dialogEl) {
1391
+ this._dialogEl.removeEventListener("cancel", this._handleDialogCancel);
1392
+ this._dialogEl.removeEventListener("click", this._handleDialogClick);
1393
+ this._dialogEl = null;
734
1394
  }
735
1395
  }
736
1396
 
1397
+ private _handleFilePickerOpen = () => {
1398
+ this._filePickerActive = true;
1399
+ this._ignoreNextEscapeClose = false;
1400
+ };
1401
+
1402
+ private _handleFilePickerClose = (
1403
+ e: CustomEvent<ComposeFilePickerCloseDetail>,
1404
+ ) => {
1405
+ this._filePickerActive = false;
1406
+ this._ignoreNextEscapeClose = Boolean(e.detail?.cancelled);
1407
+ };
1408
+
1409
+ private _handlePointerDown = () => {
1410
+ this._clearFilePickerEscapeState();
1411
+ };
1412
+
737
1413
  private _handleDialogCancel = (e: Event) => {
738
1414
  e.preventDefault();
1415
+ if (this._shouldIgnoreEscapeClose()) return;
1416
+ if (this._dismissEscapeOverlay()) return;
1417
+ this.requestClose();
1418
+ };
1419
+
1420
+ private _handleDialogClick = (e: Event) => {
1421
+ if (!this._dialogEl || e.target !== this._dialogEl) return;
1422
+
1423
+ const mouseEvent = e as MouseEvent;
1424
+ const hitTarget = document.elementFromPoint(
1425
+ mouseEvent.clientX,
1426
+ mouseEvent.clientY,
1427
+ );
1428
+ if (
1429
+ hitTarget instanceof globalThis.Element &&
1430
+ hitTarget.closest(EDITOR_FLOATING_UI_SELECTOR)
1431
+ ) {
1432
+ return;
1433
+ }
1434
+
739
1435
  this.requestClose();
740
1436
  };
741
1437
 
1438
+ private _focusPageEditorEnd() {
1439
+ this.updateComplete.then(() => this._editor?.focusInput("end"));
1440
+ }
1441
+
1442
+ private _dismissEscapeOverlay(): boolean {
1443
+ if (this._confirmPanelOpen) {
1444
+ this.requestClose();
1445
+ return true;
1446
+ }
1447
+
1448
+ if (this._addCollectionPanelOpen) {
1449
+ this._closeAddCollectionPanel();
1450
+ return true;
1451
+ }
1452
+
1453
+ if (this._editor?.isEmojiPickerOpen()) {
1454
+ this._editor.closeEmojiPicker({ restoreFocus: true });
1455
+ return true;
1456
+ }
1457
+
1458
+ if (this._showCollection) {
1459
+ this._showCollection = false;
1460
+ this._collectionSearch = "";
1461
+ this._focusPageEditorEnd();
1462
+ return true;
1463
+ }
1464
+
1465
+ if (this._showPublishPanel) {
1466
+ this._showPublishPanel = false;
1467
+ this._focusPageEditorEnd();
1468
+ return true;
1469
+ }
1470
+
1471
+ if (this._altPanelOpen) {
1472
+ this._closeAltPanel();
1473
+ this._focusPageEditorEnd();
1474
+ return true;
1475
+ }
1476
+
1477
+ if (this._draftMenuOpenId) {
1478
+ this._draftMenuOpenId = null;
1479
+ return true;
1480
+ }
1481
+
1482
+ if (this._draftsPanelOpen) {
1483
+ this._closeDraftsPanel();
1484
+ return true;
1485
+ }
1486
+
1487
+ if (this._attachedPanelOpen) {
1488
+ this._cancelAttachedPanel();
1489
+ return true;
1490
+ }
1491
+
1492
+ return false;
1493
+ }
1494
+
742
1495
  private _handleKeydown = (e: Event) => {
743
1496
  const ke = e as globalThis.KeyboardEvent;
1497
+ if (ke.key !== "Escape") {
1498
+ this._clearFilePickerEscapeState();
1499
+ }
744
1500
  if (ke.key === "Escape") {
745
1501
  ke.preventDefault();
746
1502
  ke.stopPropagation();
747
- if (this._showCollection) {
748
- this._showCollection = false;
749
- this._collectionSearch = "";
750
- } else if (this._showMoreMenu) {
751
- this._showMoreMenu = false;
752
- } else if (this._showVisibilityMenu) {
753
- this._showVisibilityMenu = false;
754
- } else if (this._addCollectionPanelOpen) {
755
- this._addCollectionPanelOpen = false;
756
- } else if (this._draftMenuOpenId) {
757
- this._draftMenuOpenId = null;
758
- } else if (this._draftsPanelOpen) {
759
- this._closeDraftsPanel();
760
- } else if (this._attachedPanelOpen) {
761
- this._cancelAttachedPanel();
762
- } else {
763
- this.requestClose();
764
- }
1503
+ if (this._shouldIgnoreEscapeClose()) return;
1504
+ if (this._dismissEscapeOverlay()) return;
1505
+ this.requestClose();
765
1506
  } else if (ke.key === "Enter" && this._confirmPanelOpen) {
766
1507
  ke.preventDefault();
767
1508
  this._handleConfirmSave();
768
1509
  } else if ((ke.metaKey || ke.ctrlKey) && ke.key === "Enter") {
769
1510
  e.preventDefault();
1511
+ if (this._attachedPanelOpen) {
1512
+ this._doneAttachedPanel();
1513
+ return;
1514
+ }
1515
+ if (!this._canPublish()) {
1516
+ this._focusBlockedSubmitField();
1517
+ return;
1518
+ }
770
1519
  this._submit("published");
771
1520
  }
772
1521
  };
@@ -798,15 +1547,34 @@ export class JantComposeDialog extends LitElement {
798
1547
  }
799
1548
 
800
1549
  private _handleFullscreenClose = (
801
- e: CustomEvent<{ json: unknown; title: string }>,
1550
+ e: CustomEvent<ComposeFullscreenCloseDetail>,
802
1551
  ) => {
803
1552
  const editor = this._editor;
804
1553
  if (editor) {
805
1554
  editor.setEditorState(
806
1555
  e.detail.json as import("@tiptap/core").JSONContent,
807
1556
  e.detail.title,
1557
+ e.detail.showTitle,
808
1558
  );
1559
+ this.updateComplete.then(() => editor.focusInput("end"));
809
1560
  }
1561
+ this._replyExpanded = e.detail.replyExpanded;
1562
+ };
1563
+
1564
+ private _buildFullscreenReplyContext(): ComposeFullscreenReplyContext | null {
1565
+ if (!this._replyToId || !this._replyToData) return null;
1566
+
1567
+ return {
1568
+ contentHtml: this._replyToData.contentHtml,
1569
+ dateText: this._replyToData.dateText,
1570
+ expanded: this._replyExpanded,
1571
+ };
1572
+ }
1573
+
1574
+ private _handleFullscreenOpen = (
1575
+ e: CustomEvent<ComposeFullscreenOpenDetail>,
1576
+ ) => {
1577
+ e.detail.replyContext = this._buildFullscreenReplyContext();
810
1578
  };
811
1579
 
812
1580
  private _handleAttachedPanelOpen = (e: Event) => {
@@ -827,16 +1595,26 @@ export class JantComposeDialog extends LitElement {
827
1595
  element: container,
828
1596
  placeholder: this.labels.attachedTextPlaceholder,
829
1597
  content,
1598
+ toolbarMode: "compose",
830
1599
  });
831
- this._attachedEditor.commands.focus();
1600
+ this._focusAttachedEditorBoundary(content);
832
1601
  });
833
1602
  };
834
1603
 
1604
+ private _focusAttachedEditorBoundary(content?: JSONContent | null) {
1605
+ if (!this._attachedEditor) return;
1606
+ const targetContent = content ?? this._attachedEditor.getJSON();
1607
+ const focusTarget: "start" | "end" = normalizeComposeDoc(targetContent)
1608
+ ? "end"
1609
+ : "start";
1610
+ this._attachedEditor.commands.focus(focusTarget);
1611
+ }
1612
+
835
1613
  private _isAttachedTextDirty(): boolean {
836
1614
  if (!this._attachedEditor) return false;
837
1615
  return (
838
- JSON.stringify(this._attachedEditor.getJSON()) !==
839
- JSON.stringify(this._attachedTextSnapshot)
1616
+ JSON.stringify(normalizeComposeDoc(this._attachedEditor.getJSON())) !==
1617
+ JSON.stringify(normalizeComposeDoc(this._attachedTextSnapshot))
840
1618
  );
841
1619
  }
842
1620
 
@@ -854,6 +1632,7 @@ export class JantComposeDialog extends LitElement {
854
1632
  const html = this._attachedEditor.getHTML();
855
1633
  this._editor?.updateAttachedText(this._attachedTextIndex, json, html);
856
1634
  }
1635
+ this._confirmForAttachedText = false;
857
1636
  this._destroyAttachedEditor();
858
1637
  this._attachedPanelOpen = false;
859
1638
  this._editor?.closeAttachedPanel(this._attachedTextIndex);
@@ -861,11 +1640,19 @@ export class JantComposeDialog extends LitElement {
861
1640
 
862
1641
  private _cancelAttachedPanel() {
863
1642
  if (this._isAttachedTextDirty()) {
864
- if (!globalThis.confirm("Discard changes?")) return;
1643
+ this._confirmForDrafts = false;
1644
+ this._confirmForAttachedText = true;
1645
+ this._confirmPanelOpen = true;
1646
+ return;
865
1647
  }
866
1648
  // Revert to snapshot — don't save current editor content
867
- this._destroyAttachedEditor();
868
- this._attachedPanelOpen = false;
1649
+ this._discardAttachedPanel();
1650
+ }
1651
+
1652
+ private _handleAttachedEditorMouseDown(event: MouseEvent) {
1653
+ if (event.target !== event.currentTarget) return;
1654
+ event.preventDefault();
1655
+ this._focusAttachedEditorBoundary();
869
1656
  }
870
1657
 
871
1658
  // ── Drafts panel ─────────────────────────────────────────────────
@@ -889,16 +1676,24 @@ export class JantComposeDialog extends LitElement {
889
1676
  try {
890
1677
  const res = await fetch("/api/posts?status=draft&limit=50");
891
1678
  if (!res.ok) throw new Error("Failed to load drafts");
892
- const json = await res.json();
893
- const posts = json.posts ?? json;
1679
+ const json = (await res.json()) as
1680
+ | DraftsResponse
1681
+ | Record<string, unknown>[];
1682
+ const posts = Array.isArray(json) ? json : (json.posts ?? []);
894
1683
  this._drafts = (posts as Record<string, unknown>[]).map(
895
1684
  (p): DraftItem => ({
896
1685
  id: p.id as string,
897
1686
  format: p.format as ComposeFormat,
898
- title: (p.title as string) ?? null,
1687
+ title:
1688
+ ((p.format as ComposeFormat) === "quote"
1689
+ ? (p.sourceName as string)
1690
+ : (p.title as string)) ?? null,
899
1691
  bodyText: (p.bodyText as string) ?? null,
900
1692
  bodyHtml: (p.bodyHtml as string) ?? null,
901
- url: (p.url as string) ?? null,
1693
+ url:
1694
+ ((p.format as ComposeFormat) === "quote"
1695
+ ? (p.sourceUrl as string)
1696
+ : (p.url as string)) ?? null,
902
1697
  quoteText: (p.quoteText as string) ?? null,
903
1698
  replyToId: (p.replyToId as string) ?? null,
904
1699
  updatedAt: p.updatedAt as number,
@@ -933,10 +1728,18 @@ export class JantComposeDialog extends LitElement {
933
1728
 
934
1729
  const res = await fetch(`/api/posts/${id}`);
935
1730
  if (!res.ok) return;
936
- const post = await res.json();
1731
+ const post = (await res.json()) as ComposePostResponse;
937
1732
 
938
1733
  this._draftSourceId = id;
939
1734
  this._format = post.format;
1735
+ this._slug = post.slug ?? "";
1736
+ this._slugTaken = false;
1737
+ this._slugCheckLoading = false;
1738
+ this._suggestedSlug = "";
1739
+ this._suggestedSlugLoading = false;
1740
+ this._slugSuggestionKey = "";
1741
+ this._visibility = post.visibility ?? "public";
1742
+ this._visibilityLocked = Boolean(post.replyToId);
940
1743
 
941
1744
  if (post.collectionIds?.length) {
942
1745
  this._collectionIds = post.collectionIds;
@@ -962,7 +1765,7 @@ export class JantComposeDialog extends LitElement {
962
1765
  // Fetch text content for TipTap text media items (stored as { json, html } envelope)
963
1766
  const textAttachments = await Promise.all(
964
1767
  textMedia.map(
965
- async (m: { id: string; url: string; summary?: string }) => {
1768
+ async (m: { id: string; url?: string; summary?: string }) => {
966
1769
  try {
967
1770
  const textRes = await fetch(`/api/media/${m.id}/content`);
968
1771
  if (textRes.ok) {
@@ -993,12 +1796,15 @@ export class JantComposeDialog extends LitElement {
993
1796
 
994
1797
  this._editor?.populate({
995
1798
  format: post.format,
996
- title: post.title ?? undefined,
1799
+ title: post.format === "quote" ? undefined : (post.title ?? undefined),
997
1800
  bodyJson: post.body ?? undefined,
998
- url: post.url ?? undefined,
1801
+ url:
1802
+ post.format === "quote"
1803
+ ? (post.sourceUrl ?? undefined)
1804
+ : (post.url ?? undefined),
999
1805
  quoteText: post.quoteText ?? undefined,
1000
1806
  quoteAuthor:
1001
- post.format === "quote" ? (post.title ?? undefined) : undefined,
1807
+ post.format === "quote" ? (post.sourceName ?? undefined) : undefined,
1002
1808
  rating: post.rating ?? undefined,
1003
1809
  media: nonTextMedia.map(
1004
1810
  (m: {
@@ -1062,6 +1868,10 @@ export class JantComposeDialog extends LitElement {
1062
1868
  private static _DRAFT_MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days
1063
1869
 
1064
1870
  private _onContentChanged = () => {
1871
+ this.requestUpdate();
1872
+ if (!this._hasManualSlug()) {
1873
+ this._scheduleSuggestedSlugRefresh();
1874
+ }
1065
1875
  // Schedule localStorage auto-save for new-post mode only
1066
1876
  if (!this._editPostId && !this._draftSourceId) {
1067
1877
  this._scheduleDraftSave();
@@ -1111,7 +1921,7 @@ export class JantComposeDialog extends LitElement {
1111
1921
  !!data.quoteText.trim() ||
1112
1922
  !!data.quoteAuthor.trim() ||
1113
1923
  data.rating > 0 ||
1114
- data.attachedTexts.some((t) => t.bodyJson !== null);
1924
+ data.attachedTexts.length > 0;
1115
1925
 
1116
1926
  if (!hasContent) {
1117
1927
  globalThis.localStorage.removeItem(JantComposeDialog._DRAFT_KEY);
@@ -1121,13 +1931,18 @@ export class JantComposeDialog extends LitElement {
1121
1931
  const draft: LocalDraft = {
1122
1932
  format: this._format,
1123
1933
  title: data.title,
1124
- bodyJson: editor._bodyJson,
1934
+ bodyJson: editor.getNormalizedBodyJson(),
1125
1935
  url: data.url,
1126
1936
  quoteText: data.quoteText,
1127
1937
  quoteAuthor: data.quoteAuthor,
1938
+ slug: this._slug,
1939
+ visibility: this._visibility,
1128
1940
  rating: data.rating,
1129
- showTitle: editor._showTitle,
1130
- showRating: editor._showRating,
1941
+ showTitle:
1942
+ this._format === "note" && data.title.trim().length > 0
1943
+ ? editor._showTitle
1944
+ : false,
1945
+ showRating: data.rating > 0 ? editor._showRating : false,
1131
1946
  collectionIds: [...this._collectionIds],
1132
1947
  replyToId: this._replyToId,
1133
1948
  attachedTexts: data.attachedTexts.map((t) => ({
@@ -1136,7 +1951,7 @@ export class JantComposeDialog extends LitElement {
1136
1951
  bodyHtml: t.bodyHtml,
1137
1952
  summary: t.summary,
1138
1953
  })),
1139
- attachmentOrder: [...(data.attachmentOrder ?? [])],
1954
+ attachmentOrder: [...data.attachmentOrder],
1140
1955
  savedAt: Date.now(),
1141
1956
  };
1142
1957
 
@@ -1185,6 +2000,13 @@ export class JantComposeDialog extends LitElement {
1185
2000
 
1186
2001
  this._format = draft.format;
1187
2002
  this._collectionIds = [...(draft.collectionIds ?? [])];
2003
+ this._slug = draft.slug ?? "";
2004
+ this._slugTaken = false;
2005
+ this._slugCheckLoading = false;
2006
+ this._suggestedSlug = "";
2007
+ this._suggestedSlugLoading = false;
2008
+ this._slugSuggestionKey = "";
2009
+ this._visibility = draft.visibility ?? "public";
1188
2010
 
1189
2011
  // Restore reply context if this draft was a reply
1190
2012
  if (draft.replyToId) {
@@ -1194,14 +2016,18 @@ export class JantComposeDialog extends LitElement {
1194
2016
 
1195
2017
  await this.updateComplete;
1196
2018
 
1197
- const textAttachments = draft.attachedTexts
1198
- ?.filter((t) => t.bodyJson !== null)
1199
- .map((t) => ({
1200
- clientId: t.clientId,
1201
- bodyJson: JSON.stringify(t.bodyJson),
1202
- bodyHtml: t.bodyHtml,
1203
- summary: t.summary,
1204
- }));
2019
+ const textAttachments = draft.attachedTexts?.flatMap((t) => {
2020
+ const bodyJson = normalizeComposeDoc(t.bodyJson);
2021
+ if (!bodyJson) return [];
2022
+ return [
2023
+ {
2024
+ clientId: t.clientId,
2025
+ bodyJson: JSON.stringify(bodyJson),
2026
+ bodyHtml: t.bodyHtml,
2027
+ summary: t.summary,
2028
+ },
2029
+ ];
2030
+ });
1205
2031
 
1206
2032
  this._editor?.populate({
1207
2033
  format: draft.format,
@@ -1412,6 +2238,9 @@ export class JantComposeDialog extends LitElement {
1412
2238
  link: this.labels.link,
1413
2239
  quote: this.labels.quote,
1414
2240
  };
2241
+ const draftButtonLabel = this._hasContent()
2242
+ ? this.labels.saveAsDraft
2243
+ : this.labels.drafts;
1415
2244
 
1416
2245
  return html`
1417
2246
  <header class="compose-dialog-header">
@@ -1447,6 +2276,7 @@ export class JantComposeDialog extends LitElement {
1447
2276
  })}
1448
2277
  @click=${() => {
1449
2278
  this._format = f;
2279
+ this._showPublishPanel = false;
1450
2280
  globalThis.requestAnimationFrame(() =>
1451
2281
  this._editor?.focusInput(),
1452
2282
  );
@@ -1460,92 +2290,24 @@ export class JantComposeDialog extends LitElement {
1460
2290
  `}
1461
2291
  </div>
1462
2292
 
1463
- <div class="flex items-center gap-0.5 shrink-0">
2293
+ <div class="compose-dialog-header-actions">
1464
2294
  ${this._editPostId
1465
2295
  ? nothing
1466
2296
  : html`<button
1467
2297
  type="button"
1468
- class="compose-dialog-header-btn"
1469
- title=${this.labels.saveDraft}
2298
+ class="compose-dialog-header-btn compose-dialog-draft-btn"
2299
+ aria-label=${draftButtonLabel}
2300
+ title=${draftButtonLabel}
1470
2301
  ?disabled=${this._loading}
1471
2302
  @click=${() => this._handleDraftButtonClick()}
1472
2303
  >
1473
- <svg
1474
- class="icon-fine"
1475
- width="18"
1476
- height="18"
1477
- viewBox="0 0 18 18"
1478
- fill="none"
1479
- stroke="currentColor"
1480
- stroke-width="1.3"
1481
- stroke-linecap="round"
1482
- stroke-linejoin="round"
1483
- >
1484
- <path d="M14 2.5L15.5 4 7 12.5l-3 .5.5-3L14 2.5z" />
1485
- <path d="M4 15h10" />
1486
- </svg>
2304
+ ${renderComposeHeaderIcon(COMPOSE_DIALOG_HEADER_ICONS.drafts)}
1487
2305
  </button>`}
1488
- ${this._renderMoreMenu()}
1489
2306
  </div>
1490
2307
  </header>
1491
2308
  `;
1492
2309
  }
1493
2310
 
1494
- private _renderMoreMenu() {
1495
- return html`
1496
- <div class="relative">
1497
- ${this._showMoreMenu
1498
- ? html`<div
1499
- class="compose-dropdown-backdrop"
1500
- @click=${() => {
1501
- this._showMoreMenu = false;
1502
- }}
1503
- ></div>`
1504
- : nothing}
1505
- <button
1506
- type="button"
1507
- class="compose-dialog-header-btn"
1508
- @click=${() => {
1509
- this._showMoreMenu = !this._showMoreMenu;
1510
- }}
1511
- >
1512
- <svg width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
1513
- <circle cx="4.5" cy="9" r="1.3" />
1514
- <circle cx="9" cy="9" r="1.3" />
1515
- <circle cx="13.5" cy="9" r="1.3" />
1516
- </svg>
1517
- </button>
1518
- ${this._showMoreMenu
1519
- ? html`
1520
- <div class="compose-dropdown compose-dropdown-right">
1521
- <button
1522
- type="button"
1523
- class="compose-dropdown-item"
1524
- @click=${() => {
1525
- this._submit("draft");
1526
- this._showMoreMenu = false;
1527
- }}
1528
- >
1529
- ${this.labels.saveAsDraft}
1530
- </button>
1531
- <div class="compose-dropdown-divider"></div>
1532
- <button
1533
- type="button"
1534
- class="compose-dropdown-item compose-dropdown-item-danger"
1535
- @click=${() => {
1536
- this._showMoreMenu = false;
1537
- this._discardAndClose();
1538
- }}
1539
- >
1540
- ${this.labels.discard}
1541
- </button>
1542
- </div>
1543
- `
1544
- : nothing}
1545
- </div>
1546
- `;
1547
- }
1548
-
1549
2311
  private _renderCollectionSelector() {
1550
2312
  const collections = this.collections ?? [];
1551
2313
  const search = this._collectionSearch.toLowerCase();
@@ -1553,6 +2315,13 @@ export class JantComposeDialog extends LitElement {
1553
2315
  ? collections.filter((c) => c.title.toLowerCase().includes(search))
1554
2316
  : collections;
1555
2317
  const selectedCount = this._collectionIds.length;
2318
+ const selectedLabel =
2319
+ selectedCount > 0
2320
+ ? this._selectedCollectionLabel(collections)
2321
+ : this.labels.collection;
2322
+ const emptyLabel = search
2323
+ ? this.labels.noCollections
2324
+ : this.labels.emptyCollections;
1556
2325
 
1557
2326
  return html`
1558
2327
  <div class="flex-1 min-w-0">
@@ -1569,133 +2338,145 @@ export class JantComposeDialog extends LitElement {
1569
2338
  <button
1570
2339
  type="button"
1571
2340
  class="compose-collection-trigger"
2341
+ aria-haspopup="listbox"
2342
+ aria-expanded=${this._showCollection ? "true" : "false"}
2343
+ data-open=${this._showCollection ? "true" : nothing}
2344
+ data-selected=${selectedCount > 0 ? "true" : nothing}
1572
2345
  @click=${() => {
2346
+ this._showPublishPanel = false;
1573
2347
  this._showCollection = !this._showCollection;
1574
2348
  if (!this._showCollection) {
1575
2349
  this._collectionSearch = "";
1576
2350
  }
1577
2351
  }}
1578
2352
  >
1579
- <svg
1580
- width="14"
1581
- height="14"
1582
- viewBox="0 0 18 18"
1583
- fill="none"
1584
- stroke="currentColor"
1585
- stroke-width="1.4"
1586
- stroke-linecap="round"
1587
- stroke-linejoin="round"
1588
- class="shrink-0 icon-fine"
1589
- >
1590
- <rect x="3" y="5" width="12" height="10" rx="2" />
1591
- <path d="M6 5V4a1 1 0 011-1h4a1 1 0 011 1v1" />
1592
- </svg>
1593
- ${selectedCount > 0
1594
- ? html`<span class="compose-collection-label"
1595
- >${this._selectedCollectionLabel(collections)}</span
1596
- >`
1597
- : html`<span>${this.labels.collection}</span>`}
1598
- <svg
1599
- width="10"
1600
- height="10"
1601
- viewBox="0 0 10 10"
1602
- fill="none"
1603
- stroke="currentColor"
1604
- stroke-width="1.4"
1605
- stroke-linecap="round"
1606
- stroke-linejoin="round"
1607
- class="shrink-0 opacity-50 icon-fine"
1608
- >
1609
- <path d="M3 4l2 2 2-2" />
1610
- </svg>
2353
+ <span class="compose-collection-trigger-icon">
2354
+ ${renderComposeCollectionPickerIcon(
2355
+ COMPOSE_COLLECTION_PICKER_ICONS.collection,
2356
+ "compose-collection-trigger-svg",
2357
+ )}
2358
+ </span>
2359
+ <span class="compose-collection-label">${selectedLabel}</span>
2360
+ ${renderComposeCollectionPickerIcon(
2361
+ COMPOSE_COLLECTION_PICKER_ICONS.chevron,
2362
+ "compose-collection-chevron",
2363
+ )}
1611
2364
  </button>
1612
2365
  <div
2366
+ class="compose-collection-popover"
1613
2367
  data-popover
1614
- data-side="top"
2368
+ data-side="bottom"
1615
2369
  aria-hidden=${this._showCollection ? "false" : "true"}
1616
2370
  >
1617
2371
  ${collections.length > 0
1618
- ? html`<header>
1619
- <svg
1620
- width="16"
1621
- height="16"
1622
- viewBox="0 0 24 24"
1623
- fill="none"
1624
- stroke="currentColor"
1625
- stroke-width="2"
1626
- stroke-linecap="round"
1627
- stroke-linejoin="round"
1628
- >
1629
- <circle cx="11" cy="11" r="8" />
1630
- <path d="m21 21-4.3-4.3" />
1631
- </svg>
1632
- <input
1633
- type="text"
1634
- role="combobox"
1635
- placeholder=${this.labels.searchCollections}
1636
- autocomplete="off"
1637
- autocorrect="off"
1638
- spellcheck="false"
1639
- .value=${this._collectionSearch}
1640
- @input=${(e: Event) => {
1641
- this._collectionSearch = (
1642
- e.target as HTMLInputElement
1643
- ).value;
1644
- }}
1645
- />
1646
- </header>`
2372
+ ? html`<div class="compose-collection-popover-header">
2373
+ <label class="compose-collection-search-shell">
2374
+ ${renderComposeCollectionPickerIcon(
2375
+ COMPOSE_COLLECTION_PICKER_ICONS.search,
2376
+ "compose-collection-search-icon",
2377
+ )}
2378
+ <input
2379
+ type="text"
2380
+ role="combobox"
2381
+ class="compose-collection-search-input"
2382
+ placeholder=${this.labels.searchCollections}
2383
+ autocomplete="off"
2384
+ autocorrect="off"
2385
+ spellcheck="false"
2386
+ .value=${this._collectionSearch}
2387
+ @input=${(e: Event) => {
2388
+ this._collectionSearch = (
2389
+ e.target as HTMLInputElement
2390
+ ).value;
2391
+ }}
2392
+ />
2393
+ </label>
2394
+ </div>`
1647
2395
  : nothing}
1648
2396
  <div
1649
2397
  role="listbox"
2398
+ class="compose-collection-options"
1650
2399
  aria-multiselectable="true"
1651
- data-empty=${filtered.length === 0
1652
- ? search
1653
- ? this.labels.noCollections
1654
- : this.labels.emptyCollections
1655
- : nothing}
1656
2400
  >
1657
- ${filtered.map(
1658
- (col) => html`
1659
- <div
1660
- role="option"
1661
- data-value=${col.id}
1662
- aria-selected=${this._collectionIds.includes(col.id)
1663
- ? "true"
1664
- : nothing}
1665
- @click=${() => this._toggleCollection(col.id)}
1666
- >
1667
- ${col.iconHtml
1668
- ? html`<span
1669
- class="inline-flex items-center justify-center w-4 h-4 shrink-0"
1670
- >${unsafeHTML(col.iconHtml)}</span
1671
- >`
1672
- : nothing}
1673
- ${col.title}
1674
- </div>
1675
- `,
1676
- )}
2401
+ ${filtered.length > 0
2402
+ ? filtered.map((col) => {
2403
+ const selected = this._collectionIds.includes(col.id);
2404
+
2405
+ return html`
2406
+ <button
2407
+ type="button"
2408
+ class=${classMap({
2409
+ "compose-collection-option": true,
2410
+ "compose-collection-option-selected": selected,
2411
+ })}
2412
+ role="option"
2413
+ data-value=${col.id}
2414
+ aria-selected=${selected ? "true" : "false"}
2415
+ @click=${() => this._toggleCollection(col.id)}
2416
+ >
2417
+ <span class="compose-collection-option-label"
2418
+ >${col.title}</span
2419
+ >
2420
+ <span
2421
+ class=${classMap({
2422
+ "compose-collection-option-marker": true,
2423
+ "compose-collection-option-marker-selected":
2424
+ selected,
2425
+ "compose-collection-option-marker-add": !selected,
2426
+ })}
2427
+ >
2428
+ ${selected
2429
+ ? html`<svg
2430
+ xmlns="http://www.w3.org/2000/svg"
2431
+ class="compose-collection-option-check-circle"
2432
+ viewBox="0 0 24 24"
2433
+ fill="none"
2434
+ aria-hidden="true"
2435
+ >
2436
+ <circle
2437
+ cx="12"
2438
+ cy="12"
2439
+ r="10"
2440
+ fill="currentColor"
2441
+ />
2442
+ <path
2443
+ d="M8 12.5 10.7 15.2 16.4 9.5"
2444
+ stroke="var(--site-page-bg)"
2445
+ stroke-width="2.3"
2446
+ stroke-linecap="round"
2447
+ stroke-linejoin="round"
2448
+ />
2449
+ </svg>`
2450
+ : renderComposeCollectionPickerIcon(
2451
+ COMPOSE_COLLECTION_PICKER_ICONS.plusCircle,
2452
+ "compose-collection-option-plus-circle",
2453
+ )}
2454
+ </span>
2455
+ </button>
2456
+ `;
2457
+ })
2458
+ : html`<div class="compose-collection-empty">
2459
+ ${emptyLabel}
2460
+ </div>`}
1677
2461
  </div>
1678
- <div
1679
- class="compose-collection-add-action"
1680
- @click=${() => {
1681
- this._showCollection = false;
1682
- this._collectionSearch = "";
1683
- this._addCollectionPanelOpen = true;
1684
- }}
1685
- >
1686
- <svg
1687
- width="14"
1688
- height="14"
1689
- viewBox="0 0 16 16"
1690
- fill="none"
1691
- stroke="currentColor"
1692
- stroke-width="1.5"
1693
- stroke-linecap="round"
1694
- stroke-linejoin="round"
2462
+ <div class="compose-collection-footer">
2463
+ <button
2464
+ type="button"
2465
+ class="compose-collection-add-action"
2466
+ @click=${() => {
2467
+ this._showCollection = false;
2468
+ this._collectionSearch = "";
2469
+ this._addCollectionPanelOpen = true;
2470
+ }}
1695
2471
  >
1696
- <path d="M8 3v10M3 8h10" />
1697
- </svg>
1698
- ${this.labels.addCollection}
2472
+ <span class="compose-collection-add-icon">
2473
+ ${renderComposeCollectionPickerIcon(
2474
+ COMPOSE_COLLECTION_PICKER_ICONS.plus,
2475
+ "compose-collection-add-svg",
2476
+ )}
2477
+ </span>
2478
+ ${this.labels.addCollection}
2479
+ </button>
1699
2480
  </div>
1700
2481
  </div>
1701
2482
  </div>
@@ -1703,7 +2484,14 @@ export class JantComposeDialog extends LitElement {
1703
2484
  `;
1704
2485
  }
1705
2486
 
1706
- // ── Add Collection panel ────────────────────────────────────────
2487
+ // ── Add Collection dialog ───────────────────────────────────────
2488
+
2489
+ private _closeAddCollectionPanel() {
2490
+ this._addCollectionPanelOpen = false;
2491
+ this.updateComplete.then(() => {
2492
+ this.querySelector<HTMLElement>(".compose-collection-trigger")?.focus();
2493
+ });
2494
+ }
1707
2495
 
1708
2496
  private async _handleAddCollectionSubmit(e: Event) {
1709
2497
  const event = e as CustomEvent<CollectionSubmitDetail>;
@@ -1723,22 +2511,42 @@ export class JantComposeDialog extends LitElement {
1723
2511
  headers: { "Content-Type": "application/json" },
1724
2512
  body: JSON.stringify(detail.data),
1725
2513
  });
1726
-
1727
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1728
-
1729
- const created = await res.json();
2514
+ const created = (await res.json().catch(() => null)) as {
2515
+ id: string;
2516
+ title: string;
2517
+ error?: string;
2518
+ } | null;
2519
+
2520
+ if (!res.ok) {
2521
+ throw new Error(
2522
+ created?.error || "Couldn't create collection. Try again.",
2523
+ );
2524
+ }
2525
+ if (!created?.id || !created.title) {
2526
+ throw new Error("Couldn't create collection. Try again.");
2527
+ }
1730
2528
  const newCollection: ComposeCollection = {
1731
2529
  id: created.id,
1732
2530
  title: created.title,
1733
- iconHtml: renderCollectionIcon(created.icon, { size: 16 }),
1734
2531
  };
1735
2532
 
1736
- this.collections = [...this.collections, newCollection];
2533
+ const refreshed = await this.refreshCollections();
2534
+ if (
2535
+ !refreshed ||
2536
+ !this.collections.some((col) => col.id === created.id)
2537
+ ) {
2538
+ this.collections = [...this.collections, newCollection];
2539
+ }
1737
2540
  this._collectionIds = [...this._collectionIds, created.id];
1738
- this._addCollectionPanelOpen = false;
1739
- showToast(this.labels.collectionFormLabels.submitLabel);
1740
- } catch {
1741
- showToast("Failed to create collection. Try again.", "error");
2541
+ this._closeAddCollectionPanel();
2542
+ showToast(this.labels.collectionFormLabels.createdLabel);
2543
+ } catch (error) {
2544
+ showToast(
2545
+ error instanceof Error
2546
+ ? error.message
2547
+ : "Couldn't create collection. Try again.",
2548
+ "error",
2549
+ );
1742
2550
  } finally {
1743
2551
  if (formEl) formEl.loading = false;
1744
2552
  }
@@ -1746,7 +2554,7 @@ export class JantComposeDialog extends LitElement {
1746
2554
 
1747
2555
  private _submitAddCollectionForm() {
1748
2556
  const form = this.querySelector<HTMLFormElement>(
1749
- ".compose-add-collection-panel form",
2557
+ "[data-collection-quick-dialog] form",
1750
2558
  );
1751
2559
  if (form) form.requestSubmit();
1752
2560
  }
@@ -1763,37 +2571,55 @@ export class JantComposeDialog extends LitElement {
1763
2571
  };
1764
2572
 
1765
2573
  return html`
1766
- <div class="compose-add-collection-panel">
1767
- <div class="compose-alt-header">
1768
- <button
1769
- type="button"
1770
- class="compose-attached-cancel"
1771
- @click=${() => {
1772
- this._addCollectionPanelOpen = false;
1773
- }}
1774
- >
1775
- ${this.labels.cancel}
1776
- </button>
1777
- <span class="compose-alt-title">${this.labels.addCollection}</span>
2574
+ <div
2575
+ class="collection-quick-dialog-backdrop"
2576
+ @click=${() => this._closeAddCollectionPanel()}
2577
+ ></div>
2578
+ <div
2579
+ class="collection-quick-dialog"
2580
+ data-collection-quick-dialog
2581
+ role="dialog"
2582
+ aria-modal="true"
2583
+ aria-label=${this.labels.addCollection}
2584
+ @click=${(event: Event) => event.stopPropagation()}
2585
+ >
2586
+ <div class="collection-quick-dialog-header">
2587
+ <div class="collection-quick-dialog-title-block">
2588
+ <h2 class="collection-quick-dialog-title">
2589
+ ${this.labels.addCollection}
2590
+ </h2>
2591
+ <p class="collection-quick-dialog-note">
2592
+ ${this.labels.collectionFormLabels.quickHint}
2593
+ </p>
2594
+ </div>
1778
2595
  <button
1779
2596
  type="button"
1780
- class="compose-post-btn ml-auto"
1781
- @click=${() => this._submitAddCollectionForm()}
2597
+ class="collection-quick-dialog-cancel"
2598
+ @click=${() => this._closeAddCollectionPanel()}
1782
2599
  >
1783
- ${this.labels.done}
2600
+ ${this.labels.collectionFormLabels.cancelLabel}
1784
2601
  </button>
1785
2602
  </div>
1786
- <div class="flex-1 overflow-y-auto">
2603
+ <div class="collection-quick-dialog-body">
1787
2604
  <jant-collection-form
1788
- class="compose-add-collection-form"
2605
+ variant="quick"
1789
2606
  .labels=${this.labels.collectionFormLabels}
1790
2607
  .initial=${initial}
1791
- action="/api/collections"
2608
+ action=${publicPath("/api/collections")}
1792
2609
  cancel-href="javascript:void(0)"
1793
2610
  @jant:collection-submit=${(e: Event) =>
1794
2611
  this._handleAddCollectionSubmit(e)}
1795
2612
  ></jant-collection-form>
1796
2613
  </div>
2614
+ <div class="collection-quick-dialog-footer">
2615
+ <button
2616
+ type="button"
2617
+ class="compose-post-btn collection-quick-dialog-submit"
2618
+ @click=${() => this._submitAddCollectionForm()}
2619
+ >
2620
+ ${this.labels.collectionFormLabels.quickSubmitLabel}
2621
+ </button>
2622
+ </div>
1797
2623
  </div>
1798
2624
  `;
1799
2625
  }
@@ -1821,7 +2647,11 @@ export class JantComposeDialog extends LitElement {
1821
2647
  </button>
1822
2648
  </div>
1823
2649
  <div class="flex-1 p-4 overflow-hidden flex flex-col">
1824
- <div class="compose-attached-tiptap compose-tiptap-body"></div>
2650
+ <div
2651
+ class="compose-attached-tiptap compose-tiptap-body"
2652
+ @mousedown=${(event: MouseEvent) =>
2653
+ this._handleAttachedEditorMouseDown(event)}
2654
+ ></div>
1825
2655
  </div>
1826
2656
  </div>
1827
2657
  `;
@@ -1905,18 +2735,26 @@ export class JantComposeDialog extends LitElement {
1905
2735
  if (!this._confirmPanelOpen) return nothing;
1906
2736
 
1907
2737
  const isEdit = !!this._editPostId;
1908
- const title = isEdit
1909
- ? this.labels.confirmEditTitle
1910
- : this.labels.confirmCloseTitle;
1911
- const subtitle = isEdit
1912
- ? this.labels.confirmEditSubtitle
1913
- : this.labels.confirmCloseSubtitle;
1914
- const saveLabel = isEdit
1915
- ? this.labels.confirmEditPublish
1916
- : this.labels.confirmCloseSave;
1917
- const discardLabel = isEdit
1918
- ? this.labels.confirmEditDiscard
1919
- : this.labels.confirmCloseDiscard;
2738
+ const title = this._confirmForAttachedText
2739
+ ? this.labels.confirmAttachedTitle
2740
+ : isEdit
2741
+ ? this.labels.confirmEditTitle
2742
+ : this.labels.confirmCloseTitle;
2743
+ const subtitle = this._confirmForAttachedText
2744
+ ? this.labels.confirmAttachedSubtitle
2745
+ : isEdit
2746
+ ? this.labels.confirmEditSubtitle
2747
+ : this.labels.confirmCloseSubtitle;
2748
+ const saveLabel = this._confirmForAttachedText
2749
+ ? this.labels.confirmAttachedSave
2750
+ : isEdit
2751
+ ? this.labels.confirmEditPublish
2752
+ : this.labels.confirmCloseSave;
2753
+ const discardLabel = this._confirmForAttachedText
2754
+ ? this.labels.confirmAttachedDiscard
2755
+ : isEdit
2756
+ ? this.labels.confirmEditDiscard
2757
+ : this.labels.confirmCloseDiscard;
1920
2758
 
1921
2759
  return html`
1922
2760
  <div class="compose-confirm-panel">
@@ -1954,14 +2792,347 @@ export class JantComposeDialog extends LitElement {
1954
2792
  private _getSubmitLabel(): string {
1955
2793
  if (this._editPostId) return this.labels.update;
1956
2794
  if (this._replyToId) return this.labels.reply;
2795
+ if (this._visibility === "latest_hidden") {
2796
+ return this.labels.postHiddenFromLatest;
2797
+ }
2798
+ if (this._visibility === "private") return this.labels.postPrivately;
1957
2799
  return this.labels.post;
1958
2800
  }
1959
2801
 
1960
- private _submitWithVisibility(visibility: ComposeVisibility) {
2802
+ private _getSlugSyncValidationMessage(): string | null {
2803
+ const issue = getSlugValidationIssue(this._slug);
2804
+ if (issue === "invalid") return this.labels.publishSlugInvalid;
2805
+ if (issue === "reserved") return this.labels.publishSlugReserved;
2806
+ return null;
2807
+ }
2808
+
2809
+ private _getSlugValidationMessage(): string | null {
2810
+ const syncMessage = this._getSlugSyncValidationMessage();
2811
+ if (syncMessage) return syncMessage;
2812
+ if (this._hasManualSlug() && this._slugTaken) {
2813
+ return this.labels.publishSlugTaken;
2814
+ }
2815
+ return null;
2816
+ }
2817
+
2818
+ private _getSlugPreviewUrl(): string | null {
2819
+ if (!this._hasManualSlug() || this._getSlugValidationMessage()) {
2820
+ return null;
2821
+ }
2822
+
2823
+ const path = publicPath(`/${this._slug.trim()}`);
2824
+ const origin =
2825
+ globalThis.location?.origin && globalThis.location.origin !== "null"
2826
+ ? globalThis.location.origin
2827
+ : "http://localhost";
2828
+ return new URL(path, `${origin}/`).toString();
2829
+ }
2830
+
2831
+ private _getSlugPreviewParts(): {
2832
+ full: string;
2833
+ origin: string;
2834
+ path: string;
2835
+ } | null {
2836
+ const previewUrl = this._getSlugPreviewUrl();
2837
+ if (!previewUrl) return null;
2838
+
2839
+ const url = new URL(previewUrl);
2840
+ return {
2841
+ full: previewUrl,
2842
+ origin: url.origin,
2843
+ path: `${url.pathname}${url.search}${url.hash}`,
2844
+ };
2845
+ }
2846
+
2847
+ private _getSlugStatusMessage(): string | null {
2848
+ if (this._hasManualSlug()) {
2849
+ if (this._getSlugValidationMessage()) {
2850
+ return this._getSlugValidationMessage();
2851
+ }
2852
+ return null;
2853
+ }
2854
+
2855
+ if (this._suggestedSlugLoading) {
2856
+ return this.labels.publishSlugGenerating;
2857
+ }
2858
+
2859
+ return null;
2860
+ }
2861
+
2862
+ private _revealSlugField() {
2863
+ this._showCollection = false;
2864
+ this._collectionSearch = "";
2865
+ this._showPublishPanel = true;
2866
+ this._confirmPanelOpen = false;
2867
+ this._scheduleSuggestedSlugRefresh(true);
2868
+ this.updateComplete.then(() => {
2869
+ this.querySelector<HTMLInputElement>(
2870
+ ".compose-publish-slug-input",
2871
+ )?.focus();
2872
+ });
2873
+ }
2874
+
2875
+ private _canPublish(): boolean {
2876
+ if (this._loading) return false;
2877
+ const editor = this._editor;
2878
+ if (!editor) return false;
2879
+ if (this._getSlugValidationMessage()) return false;
2880
+ if (editor.getUrlValidationMessage()) return false;
2881
+ if (editor.getLinkTitleValidationMessage()) return false;
2882
+
2883
+ const data = editor.getData();
2884
+ if (this._format === "link") {
2885
+ return data.url.trim().length > 0;
2886
+ }
2887
+ if (this._format === "quote") {
2888
+ return data.quoteText.trim().length > 0;
2889
+ }
2890
+ return this._hasContent();
2891
+ }
2892
+
2893
+ private _togglePublishPanel() {
2894
+ this._showCollection = false;
2895
+ this._collectionSearch = "";
2896
+ const nextOpen = !this._showPublishPanel;
2897
+ this._showPublishPanel = nextOpen;
2898
+ if (nextOpen) {
2899
+ this._scheduleSuggestedSlugRefresh(true);
2900
+ }
2901
+ }
2902
+
2903
+ private _setVisibility(visibility: ComposeVisibility) {
2904
+ if (this._visibilityLocked) return;
1961
2905
  this._visibility = visibility;
1962
- this._showVisibilityMenu = false;
1963
- // Wait for state to update before submitting
1964
- this.updateComplete.then(() => this._submit("published"));
2906
+ if (!this._editPostId && !this._draftSourceId && !this._replyToId) {
2907
+ JantComposeDialog._lastNewPostVisibility = visibility;
2908
+ }
2909
+ this._showPublishPanel = false;
2910
+ }
2911
+
2912
+ private _onSlugInput(e: Event) {
2913
+ const value = (e.target as HTMLInputElement).value;
2914
+ this._slug = value.toLowerCase();
2915
+ this._slugTaken = false;
2916
+ this._slugCheckLoading = false;
2917
+ if (this._hasManualSlug()) {
2918
+ this._scheduleSlugAvailabilityCheck();
2919
+ return;
2920
+ }
2921
+ this._scheduleSuggestedSlugRefresh();
2922
+ }
2923
+
2924
+ private _renderVisibilityIcon(
2925
+ visibility: ComposeVisibility,
2926
+ variant: "menu" | "toggle" = "menu",
2927
+ ) {
2928
+ const iconClasses = classMap({
2929
+ "compose-publish-visibility-icon": true,
2930
+ "compose-publish-visibility-icon-toggle": variant === "toggle",
2931
+ "compose-publish-visibility-icon-public": visibility === "public",
2932
+ "compose-publish-visibility-icon-latest_hidden":
2933
+ visibility === "latest_hidden",
2934
+ "compose-publish-visibility-icon-private": visibility === "private",
2935
+ });
2936
+ return renderComposePublishVisibilityIcon(
2937
+ COMPOSE_PUBLISH_VISIBILITY_ICONS[visibility],
2938
+ iconClasses,
2939
+ );
2940
+ }
2941
+
2942
+ private _renderPublishVisibilityOption(
2943
+ visibility: ComposeVisibility,
2944
+ label: string,
2945
+ hint: string,
2946
+ ) {
2947
+ const selected = this._visibility === visibility;
2948
+
2949
+ return html`
2950
+ <button
2951
+ type="button"
2952
+ class=${classMap({
2953
+ "compose-publish-option": true,
2954
+ "compose-publish-option-selected": selected,
2955
+ })}
2956
+ role="radio"
2957
+ aria-checked=${selected ? "true" : "false"}
2958
+ ?disabled=${this._visibilityLocked}
2959
+ @click=${() => this._setVisibility(visibility)}
2960
+ >
2961
+ ${this._renderVisibilityIcon(visibility)}
2962
+ <span class="compose-publish-copy">
2963
+ <span class="compose-publish-row-label">${label}</span>
2964
+ <span class="compose-publish-row-hint">${hint}</span>
2965
+ </span>
2966
+ ${selected
2967
+ ? renderComposePublishActionIcon(
2968
+ COMPOSE_PUBLISH_ACTION_ICONS.check,
2969
+ "compose-publish-row-check",
2970
+ )
2971
+ : nothing}
2972
+ </button>
2973
+ `;
2974
+ }
2975
+
2976
+ private _renderPublishSlugSection() {
2977
+ const slugError = this._getSlugValidationMessage();
2978
+ const slugStatus = this._getSlugStatusMessage();
2979
+ const slugPreview = this._getSlugPreviewParts();
2980
+ const showSuggestion =
2981
+ !this._hasManualSlug() &&
2982
+ !this._suggestedSlugLoading &&
2983
+ Boolean(this._suggestedSlug);
2984
+
2985
+ return html`
2986
+ <section class="compose-publish-section">
2987
+ <div class="compose-publish-section-header">
2988
+ <div class="compose-publish-section-copy">
2989
+ <p class="compose-publish-section-label">
2990
+ ${this.labels.publishSlugLabel}
2991
+ </p>
2992
+ <p class="compose-publish-section-hint">
2993
+ ${this.labels.publishSlugHint}
2994
+ </p>
2995
+ </div>
2996
+ ${this._hasManualSlug()
2997
+ ? html`
2998
+ <button
2999
+ type="button"
3000
+ class="compose-publish-section-action"
3001
+ @click=${() => this._resetCustomSlug()}
3002
+ >
3003
+ ${this.labels.publishSlugReset}
3004
+ </button>
3005
+ `
3006
+ : nothing}
3007
+ </div>
3008
+ <div class="compose-publish-slug-field">
3009
+ <div class="compose-publish-slug-input-wrap">
3010
+ <span class="compose-publish-slug-prefix">/</span>
3011
+ <input
3012
+ type="text"
3013
+ class="compose-input compose-publish-slug-input"
3014
+ .value=${this._slug}
3015
+ placeholder=${this.labels.publishSlugPlaceholder}
3016
+ aria-invalid=${slugError ? "true" : "false"}
3017
+ spellcheck="false"
3018
+ autocapitalize="off"
3019
+ autocomplete="off"
3020
+ @input=${(e: Event) => this._onSlugInput(e)}
3021
+ />
3022
+ </div>
3023
+ ${showSuggestion
3024
+ ? html`
3025
+ <button
3026
+ type="button"
3027
+ class="compose-slug-suggestion"
3028
+ @click=${() => this._useSuggestedSlug()}
3029
+ >
3030
+ <span class="compose-slug-suggestion-copy">
3031
+ <span class="compose-slug-suggestion-label"
3032
+ >${this.labels.publishSlugSuggested}</span
3033
+ >
3034
+ <span class="compose-slug-suggestion-chip">
3035
+ <span class="compose-slug-suggestion-value"
3036
+ >/${this._suggestedSlug}</span
3037
+ >
3038
+ </span>
3039
+ </span>
3040
+ <span class="compose-slug-suggestion-icon" aria-hidden="true">
3041
+ <svg
3042
+ width="14"
3043
+ height="14"
3044
+ viewBox="0 0 14 14"
3045
+ fill="none"
3046
+ stroke="currentColor"
3047
+ stroke-width="1.4"
3048
+ stroke-linecap="round"
3049
+ stroke-linejoin="round"
3050
+ >
3051
+ <path d="M3 7h8" />
3052
+ <path d="m8 3 4 4-4 4" />
3053
+ </svg>
3054
+ </span>
3055
+ </button>
3056
+ `
3057
+ : nothing}
3058
+ ${slugStatus
3059
+ ? html`<p
3060
+ class=${classMap({
3061
+ "compose-publish-slug-status": true,
3062
+ "compose-publish-slug-status-error": Boolean(slugError),
3063
+ })}
3064
+ data-compose-slug-error=${slugError ? "true" : nothing}
3065
+ >
3066
+ ${slugStatus}
3067
+ </p>`
3068
+ : nothing}
3069
+ ${slugPreview
3070
+ ? html`<p
3071
+ class="compose-publish-slug-preview"
3072
+ data-compose-slug-preview
3073
+ title=${slugPreview.full}
3074
+ >
3075
+ <span class="compose-publish-slug-preview-origin"
3076
+ >${slugPreview.origin}</span
3077
+ ><span class="compose-publish-slug-preview-path"
3078
+ >${slugPreview.path}</span
3079
+ >
3080
+ </p>`
3081
+ : nothing}
3082
+ </div>
3083
+ </section>
3084
+ `;
3085
+ }
3086
+
3087
+ private _renderPublishPanel() {
3088
+ if (!this._showPublishPanel) return nothing;
3089
+
3090
+ return html`
3091
+ <div
3092
+ class="compose-publish-panel"
3093
+ role="dialog"
3094
+ aria-label=${this.labels.publishSettings}
3095
+ data-compose-publish-panel
3096
+ >
3097
+ ${this._visibilityLocked
3098
+ ? nothing
3099
+ : html`
3100
+ <section class="compose-publish-section">
3101
+ <div class="compose-publish-section-header">
3102
+ <div class="compose-publish-section-copy">
3103
+ <p class="compose-publish-section-label">
3104
+ ${this.labels.publishVisibilityLabel}
3105
+ </p>
3106
+ </div>
3107
+ </div>
3108
+ <div class="compose-publish-list" role="radiogroup">
3109
+ ${this._renderPublishVisibilityOption(
3110
+ "public",
3111
+ this.labels.publishVisibilityPublic,
3112
+ this.labels.publishVisibilityPublicHint,
3113
+ )}
3114
+ ${this._renderPublishVisibilityOption(
3115
+ "latest_hidden",
3116
+ this.labels.publishVisibilityHiddenFromLatest,
3117
+ this.labels.publishVisibilityHiddenFromLatestHint,
3118
+ )}
3119
+ ${this._renderPublishVisibilityOption(
3120
+ "private",
3121
+ this.labels.publishVisibilityPrivate,
3122
+ this.labels.publishVisibilityPrivateHint,
3123
+ )}
3124
+ </div>
3125
+ </section>
3126
+ `}
3127
+ ${this._visibilityLocked
3128
+ ? nothing
3129
+ : html`<div
3130
+ class="compose-publish-divider"
3131
+ aria-hidden="true"
3132
+ ></div>`}
3133
+ ${this._renderPublishSlugSection()}
3134
+ </div>
3135
+ `;
1965
3136
  }
1966
3137
 
1967
3138
  private _renderPublishButton() {
@@ -1978,143 +3149,56 @@ export class JantComposeDialog extends LitElement {
1978
3149
  >
1979
3150
  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
1980
3151
  </svg>`;
1981
-
1982
- // In edit mode or reply mode, show a simple button (no visibility split)
1983
- if (this._editPostId || this._replyToId) {
1984
- return html`
1985
- <button
1986
- type="button"
1987
- class="compose-post-btn"
1988
- ?disabled=${this._loading}
1989
- @click=${() => this._submit("published")}
1990
- >
1991
- ${this._loading ? spinner : nothing} ${this._getSubmitLabel()}
1992
- </button>
1993
- `;
1994
- }
3152
+ const canPublish = this._canPublish();
1995
3153
 
1996
3154
  return html`
1997
3155
  <div class="compose-publish-group">
1998
- ${this._showVisibilityMenu
3156
+ ${this._showPublishPanel
1999
3157
  ? html`<div
2000
3158
  class="compose-dropdown-backdrop"
2001
3159
  @click=${() => {
2002
- this._showVisibilityMenu = false;
3160
+ this._showPublishPanel = false;
2003
3161
  }}
2004
3162
  ></div>`
2005
3163
  : nothing}
2006
- <button
2007
- type="button"
2008
- class="compose-publish-main"
2009
- ?disabled=${this._loading}
2010
- @click=${() => this._submit("published")}
2011
- >
2012
- ${this._loading ? spinner : nothing} ${this._getSubmitLabel()}
2013
- </button>
2014
- <button
2015
- type="button"
2016
- class="compose-publish-toggle"
2017
- ?disabled=${this._loading}
2018
- aria-haspopup="menu"
2019
- aria-expanded=${this._showVisibilityMenu}
2020
- @click=${() => {
2021
- this._showVisibilityMenu = !this._showVisibilityMenu;
2022
- }}
3164
+ <div
3165
+ role="group"
3166
+ class=${classMap({
3167
+ "compose-publish-buttons": true,
3168
+ "compose-publish-buttons-inactive": !canPublish && !this._loading,
3169
+ })}
2023
3170
  >
2024
- <svg
2025
- width="14"
2026
- height="14"
2027
- viewBox="0 0 24 24"
2028
- fill="none"
2029
- stroke="currentColor"
2030
- stroke-width="2.5"
2031
- stroke-linecap="round"
2032
- stroke-linejoin="round"
3171
+ <button
3172
+ type="button"
3173
+ class=${classMap({
3174
+ "compose-publish-main": true,
3175
+ "compose-publish-main-loading": this._loading,
3176
+ })}
3177
+ ?disabled=${!canPublish}
3178
+ @click=${() => this._submit("published")}
2033
3179
  >
2034
- <path d="m6 9 6 6 6-6" />
2035
- </svg>
2036
- </button>
2037
- ${this._showVisibilityMenu
2038
- ? html`
2039
- <div class="compose-dropdown" role="menu">
2040
- <button
2041
- type="button"
2042
- class="compose-dropdown-item"
2043
- role="menuitem"
2044
- @click=${() => {
2045
- this._featured = true;
2046
- this._showVisibilityMenu = false;
2047
- this.updateComplete.then(() => this._submit("published"));
2048
- }}
2049
- >
2050
- <svg
2051
- width="16"
2052
- height="16"
2053
- viewBox="0 0 24 24"
2054
- fill="none"
2055
- stroke="currentColor"
2056
- stroke-width="2"
2057
- stroke-linecap="round"
2058
- stroke-linejoin="round"
2059
- >
2060
- <path
2061
- d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"
2062
- />
2063
- </svg>
2064
- ${this.labels.publishFeatured}
2065
- </button>
2066
- <button
2067
- type="button"
2068
- class="compose-dropdown-item"
2069
- role="menuitem"
2070
- @click=${() => this._submitWithVisibility("unlisted")}
2071
- >
2072
- <svg
2073
- width="16"
2074
- height="16"
2075
- viewBox="0 0 24 24"
2076
- fill="none"
2077
- stroke="currentColor"
2078
- stroke-width="2"
2079
- stroke-linecap="round"
2080
- stroke-linejoin="round"
2081
- >
2082
- <path d="M9 17H7A5 5 0 0 1 7 7h2" />
2083
- <path d="M15 7h2a5 5 0 1 1 0 10h-2" />
2084
- <line x1="8" x2="16" y1="12" y2="12" />
2085
- </svg>
2086
- ${this.labels.publishUnlisted}
2087
- </button>
2088
- <button
2089
- type="button"
2090
- class="compose-dropdown-item"
2091
- role="menuitem"
2092
- @click=${() => this._submitWithVisibility("private")}
2093
- >
2094
- <svg
2095
- width="16"
2096
- height="16"
2097
- viewBox="0 0 24 24"
2098
- fill="none"
2099
- stroke="currentColor"
2100
- stroke-width="2"
2101
- stroke-linecap="round"
2102
- stroke-linejoin="round"
2103
- >
2104
- <path
2105
- d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"
2106
- />
2107
- <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
2108
- <path
2109
- d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"
2110
- />
2111
- <path d="m2 2 20 20" />
2112
- </svg>
2113
- ${this.labels.publishPrivate}
2114
- </button>
2115
- </div>
2116
- `
2117
- : nothing}
3180
+ ${this._loading ? spinner : nothing} ${this._getSubmitLabel()}
3181
+ </button>
3182
+ <button
3183
+ type="button"
3184
+ class=${classMap({
3185
+ "compose-publish-toggle": true,
3186
+ "compose-publish-toggle-loading": this._loading,
3187
+ })}
3188
+ ?disabled=${this._loading}
3189
+ aria-haspopup="dialog"
3190
+ aria-expanded=${this._showPublishPanel ? "true" : "false"}
3191
+ aria-label=${this.labels.publishSettings}
3192
+ title=${this.labels.publishSettings}
3193
+ @click=${() => this._togglePublishPanel()}
3194
+ >
3195
+ ${renderComposePublishActionIcon(
3196
+ COMPOSE_PUBLISH_ACTION_ICONS.chevron,
3197
+ "compose-publish-toggle-chevron",
3198
+ )}
3199
+ </button>
3200
+ </div>
3201
+ ${this._renderPublishPanel()}
2118
3202
  </div>
2119
3203
  `;
2120
3204
  }
@@ -2132,7 +3216,10 @@ export class JantComposeDialog extends LitElement {
2132
3216
  class=${classMap({
2133
3217
  "compose-dialog-inner": true,
2134
3218
  "compose-dialog-inner-page": this.pageMode,
3219
+ "compose-dialog-inner-suspended": this._addCollectionPanelOpen,
2135
3220
  })}
3221
+ aria-hidden=${this._addCollectionPanelOpen ? "true" : "false"}
3222
+ ?inert=${this._addCollectionPanelOpen}
2136
3223
  >
2137
3224
  ${this._renderHeader()}
2138
3225
  ${isReply