@jant/core 0.3.40 → 0.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -34
- package/bin/commands/assets/upload.js +247 -0
- package/bin/commands/collections.js +268 -0
- package/bin/commands/import-site.js +1169 -343
- package/bin/commands/media.js +302 -0
- package/bin/commands/migrate-text-attachments.js +183 -0
- package/bin/commands/migrate.js +263 -2
- package/bin/commands/posts.js +262 -0
- package/bin/commands/search-reindex.js +175 -0
- package/bin/commands/search.js +53 -0
- package/bin/commands/settings.js +93 -0
- package/bin/commands/site/export.js +34 -33
- package/bin/commands/site/{localize-media.js → pull-media.js} +17 -17
- package/bin/lib/d1-query.js +15 -3
- package/bin/lib/http-api.js +223 -0
- package/bin/lib/hugo-markdown.js +102 -0
- package/bin/lib/media-upload.js +206 -0
- package/bin/lib/migration-runner.js +64 -0
- package/bin/lib/runtime-target.js +7 -2
- package/bin/lib/site-media-parser.js +71 -16
- package/bin/lib/site-pull-media.js +695 -0
- package/bin/lib/site-snapshot.js +0 -1
- package/dist/app-Ctl0T0zO.js +5 -0
- package/dist/{app-CAtsuLLh.js → app-GbfwoeDJ.js} +24357 -23139
- package/dist/client/.vite/manifest.json +3485 -0
- package/dist/client/_assets/chunks/02611a045a7fe83a12014e3debc9f731-BqEDtwXC.woff2 +0 -0
- package/dist/client/_assets/chunks/033466ef683afe931f7f520cfb42d928-BluLHEh9.woff2 +0 -0
- package/dist/client/_assets/chunks/04b8bde59cff68eeee74fb1914eb09c7-D5j2CLTt.woff2 +0 -0
- package/dist/client/_assets/chunks/057a6a98bda7fe57105ddaa99ec82015-DY_AoijT.woff2 +0 -0
- package/dist/client/_assets/chunks/05ac821472e235943ed1435e4bb8ecad-CUm8GiH6.woff2 +0 -0
- package/dist/client/_assets/chunks/05da12edb9d52210581dc6ec4541031f-BXAOx2XP.woff2 +0 -0
- package/dist/client/_assets/chunks/074c1f8483d5a4d8c45c8c5f3e4cbb32-CDqdMQm8.woff2 +0 -0
- package/dist/client/_assets/chunks/0968e4861204b51f62a2f8e9f15dd5e0-D5tLgF_8.woff2 +0 -0
- package/dist/client/_assets/chunks/0acd1fe2b2ea1ad1bfee7ae1fa139d27-BKK1m412.woff2 +0 -0
- package/dist/client/_assets/chunks/0c2ab6b295e55f356f8020d4e7747522-CkCQjQw3.woff2 +0 -0
- package/dist/client/_assets/chunks/0c5f9492af03a4fa42c784de94649de1-CQlJqksa.woff2 +0 -0
- package/dist/client/_assets/chunks/0d5dec931dc885f07fe5cd5af8bed675-C74x-U_U.woff2 +0 -0
- package/dist/client/_assets/chunks/0d9a936885a4c39077438effd3779cbd-Bw0fYF4S.woff2 +0 -0
- package/dist/client/_assets/chunks/0e645da524f7cfc0e8c3c03fb2b08428-BVeI_5Th.woff2 +0 -0
- package/dist/client/_assets/chunks/0f5e1a18987dbc84ca05188c129e1936-B6_lZ6fE.woff2 +0 -0
- package/dist/client/_assets/chunks/1076f0f6f66d28d7a2f16427faad4413-ubiXnD1P.woff2 +0 -0
- package/dist/client/_assets/chunks/112743a4ab5fdd1498dfdf2b11336380-B5FzQxTK.woff2 +0 -0
- package/dist/client/_assets/chunks/1211f03d3ff5759a702631445793f60e-DDcSiyCt.woff2 +0 -0
- package/dist/client/_assets/chunks/12ef1ba76bd20b004b865266a1aa70b3-BwmaD87S.woff2 +0 -0
- package/dist/client/_assets/chunks/135e83b403475c5dc9e49b43501a5b84-C5mE_KZS.woff2 +0 -0
- package/dist/client/_assets/chunks/1380210a722ac9aeef54005ad7b015c9-CjA-MP82.woff2 +0 -0
- package/dist/client/_assets/chunks/13b2c53b30e6a3e4de7132dbc18dbdfc-B9WOB7kv.woff2 +0 -0
- package/dist/client/_assets/chunks/147b24c2f08d03bbed30887d4cb3311c-IeeFCJ13.woff2 +0 -0
- package/dist/client/_assets/chunks/152395634a207579552f8cb54db88599-C2y2JyNZ.woff2 +0 -0
- package/dist/client/_assets/chunks/15d95680dc31cc6ce20e0a5464106dc4-CpD8dqRm.woff2 +0 -0
- package/dist/client/_assets/chunks/16663e567f81f4725a1522f37e18f71f-BNoxS9pQ.woff2 +0 -0
- package/dist/client/_assets/chunks/1733f27476507ca68b68a803bc533cc2-DXL-14Ug.woff2 +0 -0
- package/dist/client/_assets/chunks/17c7f5d0a45e92ede0e5dec3a2c77efa-CNj3jQMl.woff2 +0 -0
- package/dist/client/_assets/chunks/1824321da801e6257b902f3d1d09054c-D4dgdyWB.woff2 +0 -0
- package/dist/client/_assets/chunks/188c2db794f3dd7a45889ddbc81da9bb-C9NbUBBO.woff2 +0 -0
- package/dist/client/_assets/chunks/1a08931435f885e707edb85978ea3bb6-BOiP2u7G.woff2 +0 -0
- package/dist/client/_assets/chunks/1a3a92c7c060a71a6c35819b9ebcdbb9-DFfonQfz.woff2 +0 -0
- package/dist/client/_assets/chunks/1be602ad456b0d75902d352116cb35fd-Bc-XQtQh.woff2 +0 -0
- package/dist/client/_assets/chunks/1cf737900dd49c2e88f1b3221a82a602-CfTmnns3.woff2 +0 -0
- package/dist/client/_assets/chunks/1fba7ec0e412e911bf31841de5a8a4e4-18ELMbFF.woff2 +0 -0
- package/dist/client/_assets/chunks/233d3a685ee18276b319363474599d47-BaFdJ0-G.woff2 +0 -0
- package/dist/client/_assets/chunks/2612c60f9dc5459ac42763591240a1d6-CTD69cq1.woff2 +0 -0
- package/dist/client/_assets/chunks/284b53bbefb06924cf236d24c8ed5641-CPDiuOv6.woff2 +0 -0
- package/dist/client/_assets/chunks/28a97af9ab9d38983d20f39ff09840e1-mFqcT3Jw.woff2 +0 -0
- package/dist/client/_assets/chunks/2a56eaf19d1d38a6b57e2a388f733676-CU52qBlE.woff2 +0 -0
- package/dist/client/_assets/chunks/2ab8cf1f23a5ac7939a7876054e711a7-B4T3dK7A.woff2 +0 -0
- package/dist/client/_assets/chunks/2abfbab82b6a7c04426afc054d2464da-D5PIIHde.woff2 +0 -0
- package/dist/client/_assets/chunks/2bfaadaf3479c72286248e6de0be0ec9-DEpU6O74.woff2 +0 -0
- package/dist/client/_assets/chunks/2c8c55e4cec85ce77e95cac9d330f5cf-BCTA96EZ.woff2 +0 -0
- package/dist/client/_assets/chunks/2e6f4bb71ef6b38765d51acc5a79f638-Mbs6PECD.woff2 +0 -0
- package/dist/client/_assets/chunks/2ed9981d2e8983365bd051159b976b6d-D8gg6NWM.woff2 +0 -0
- package/dist/client/_assets/chunks/2f4c633e923ba30c6ba376367379fc91-CIJCFztv.woff2 +0 -0
- package/dist/client/_assets/chunks/31194d303a67561926a544ed0e072aee-w3-eGYAi.woff2 +0 -0
- package/dist/client/_assets/chunks/31a197213ae61d7043c81013f52d10d6-DXZrrkJP.woff2 +0 -0
- package/dist/client/_assets/chunks/349965eee0a9b6c984a319ab96a4ece9-DByAVmXw.woff2 +0 -0
- package/dist/client/_assets/chunks/34e6750e00c3a911ef87dc66b336594d-DNbWesOu.woff2 +0 -0
- package/dist/client/_assets/chunks/3623d96698132c61e861f984ce11cc23-jqV9ErzX.woff2 +0 -0
- package/dist/client/_assets/chunks/36cda3eae13370b934bba4429fab9078-BPDcUlw9.woff2 +0 -0
- package/dist/client/_assets/chunks/36f3df4730cfca4fc77fe52b52e6a126-BwK10NC0.woff2 +0 -0
- package/dist/client/_assets/chunks/37887deb7a9c466cf2af6ee7ff372ea6-BrAerJB1.woff2 +0 -0
- package/dist/client/_assets/chunks/37dbd564820cce2f7b3331e442da0df3-Cql2kzIi.woff2 +0 -0
- package/dist/client/_assets/chunks/37dc8505b20b37a2477a9121b16d44a7-CoXw6XWV.woff2 +0 -0
- package/dist/client/_assets/chunks/39211b02f24c69cbd1b54f6d41b01933-4Xu7vvnq.woff2 +0 -0
- package/dist/client/_assets/chunks/3aa13f1843d7c8fa16109b6395a9a29d-BQFZUc3B.woff2 +0 -0
- package/dist/client/_assets/chunks/3aba89c4d4281553078de4622b498cbb-8QS1zDYk.woff2 +0 -0
- package/dist/client/_assets/chunks/3ac0a07878cacdc98bb889f40f5970d3-C-BaM6-y.woff2 +0 -0
- package/dist/client/_assets/chunks/3d88558097559d775231194d43745ba7-CR2aqGC7.woff2 +0 -0
- package/dist/client/_assets/chunks/3dc3e8c35524cad2d1236f3393104ccf-9sqBsF7B.woff2 +0 -0
- package/dist/client/_assets/chunks/3e24063b19abd2b051af986a6870046d-TlweM3SW.woff2 +0 -0
- package/dist/client/_assets/chunks/3e2c06bdd9dcab29aeaeb0cfb7fa608a-BaqU-dIt.woff2 +0 -0
- package/dist/client/_assets/chunks/424123f9371ba357087363263eeafcda-C8SVfEJi.woff2 +0 -0
- package/dist/client/_assets/chunks/43b3bbf43055c3e65db9cdf2c5b18c1c-CbHx6-Ki.woff2 +0 -0
- package/dist/client/_assets/chunks/4410f876d27c9821839474b6b1059fd7-ClYz91aY.woff2 +0 -0
- package/dist/client/_assets/chunks/44704471b60c18b3ecec5300a88f1102-DDmmy6-m.woff2 +0 -0
- package/dist/client/_assets/chunks/45140742dfa8686b58db2899ce89f89a-D5IMMzDM.woff2 +0 -0
- package/dist/client/_assets/chunks/46604090efbb99c2db1800928af0a239-JX2fpYX_.woff2 +0 -0
- package/dist/client/_assets/chunks/46998df85d31e629c0142f0556f5e7c5-BIkNG9Wh.woff2 +0 -0
- package/dist/client/_assets/chunks/469d2754e315e77270c12fa0ab28026c-D-y-vsfA.woff2 +0 -0
- package/dist/client/_assets/chunks/479329a39a3eb6c0047e6b0981919855-vZKCRx42.woff2 +0 -0
- package/dist/client/_assets/chunks/491bbba374093fff045f55803a22bfef-DSQXiDCd.woff2 +0 -0
- package/dist/client/_assets/chunks/49902f1c41b281f8eb3771abdd4dccb8-DxVxNg2W.woff2 +0 -0
- package/dist/client/_assets/chunks/4c0760284569b87d8f2290c324953973-Bqd-TKHH.woff2 +0 -0
- package/dist/client/_assets/chunks/4cba92c942694bdc479bc54101bb1aa4-BrfsrgrC.woff2 +0 -0
- package/dist/client/_assets/chunks/4d38e56738156625329d93bd5c8cc74a-DONkMP0y.woff2 +0 -0
- package/dist/client/_assets/chunks/4db4f31a16965baa42cb7dad667a8f04-Dt1Cy4gr.woff2 +0 -0
- package/dist/client/_assets/chunks/4e7d263c30bb48604069a3b4404d4eeb-DNPCkvKD.woff2 +0 -0
- package/dist/client/_assets/chunks/5279c7e465d9871edf51fd02e691eee7-CKhyhvVf.woff2 +0 -0
- package/dist/client/_assets/chunks/527ff336279465617aafbafe1a830632-CC3lv4pm.woff2 +0 -0
- package/dist/client/_assets/chunks/54bfdb6f21f9d6191e5100580239e14f-PGlqYvgl.woff2 +0 -0
- package/dist/client/_assets/chunks/56b40518ea0608e62826bd44ee27f5f9-BD9F9Oss.woff2 +0 -0
- package/dist/client/_assets/chunks/56be5e32fd01668fe5ba814d94905839-CSyFA-9l.woff2 +0 -0
- package/dist/client/_assets/chunks/572b9e28f1e6d89b765a16d809db84a1-BIYMjzEa.woff2 +0 -0
- package/dist/client/_assets/chunks/57e9a86651366c1ba299e47aa7e358c2-BHuZXB4W.woff2 +0 -0
- package/dist/client/_assets/chunks/57f89adeae55aa7fe2add3fc1801ce03-Cy3PNrfx.woff2 +0 -0
- package/dist/client/_assets/chunks/5921cef36750ff6092d94b83a802823b-hF-uymTh.woff2 +0 -0
- package/dist/client/_assets/chunks/59e95d8b5332dcdae894a19946ab767d-ClRfsjWZ.woff2 +0 -0
- package/dist/client/_assets/chunks/5acc7f736217259db79a42bf44241c48-FYQUuFOM.woff2 +0 -0
- package/dist/client/_assets/chunks/5c05f8b75c9d7f25b3c04ca711cf43b3-D-YWPkSq.woff2 +0 -0
- package/dist/client/_assets/chunks/5df9ce98c75f8c50fb4fff7f01a23925-DgoCENyW.woff2 +0 -0
- package/dist/client/_assets/chunks/5e0124c7265f1b4cf0fc797b94efbebc-BTBNHsSB.woff2 +0 -0
- package/dist/client/_assets/chunks/5ec349ff62b9ee5fe0c6dae867704dc4-B4dezhrz.woff2 +0 -0
- package/dist/client/_assets/chunks/60020a830d809c26970cf8e48ba1eeda-B6FDTSik.woff2 +0 -0
- package/dist/client/_assets/chunks/601f812e8b1eafecd5ba585a6a6d7962-gIVBlUU9.woff2 +0 -0
- package/dist/client/_assets/chunks/6025ddaf99d86cc8bfd781006b19fbd6-CS-ze337.woff2 +0 -0
- package/dist/client/_assets/chunks/60bcfa3ea7446eb42394aab02d28be40-C-PQfCu3.woff2 +0 -0
- package/dist/client/_assets/chunks/60f6e28b3f0400b0e8fe32c3c7116102-Dag9L2qU.woff2 +0 -0
- package/dist/client/_assets/chunks/615a7e0d63f257109d599b2a1977de68-D6Y93yoG.woff2 +0 -0
- package/dist/client/_assets/chunks/615b38e0b5bb3ea68426f44f47706e6f-B5yzIIer.woff2 +0 -0
- package/dist/client/_assets/chunks/61a2c80d0c924d5a6187c02e8d1d1642-BY3MmHbZ.woff2 +0 -0
- package/dist/client/_assets/chunks/62fc94f8d4c5a750b7f25e7387539910-3ql2_WDK.woff2 +0 -0
- package/dist/client/_assets/chunks/639c7c6b0b0c27c738702741cfa4b8c0-BCD3OGri.woff2 +0 -0
- package/dist/client/_assets/chunks/63fafcb069520613d0ea35ad3e6b1e42-DP0Rrj4o.woff2 +0 -0
- package/dist/client/_assets/chunks/64a404d675f1d726f0891b7a80794595-DU3L0I9s.woff2 +0 -0
- package/dist/client/_assets/chunks/65a60d87c64228d258a123cbe85f5f31-B4kbf-79.woff2 +0 -0
- package/dist/client/_assets/chunks/65e9c4585d71bf48a5c62f367010da16-CvzgzReN.woff2 +0 -0
- package/dist/client/_assets/chunks/68a3a4bf337f8a0722be76676e20b850-Dnaw8EPD.woff2 +0 -0
- package/dist/client/_assets/chunks/6968e889e18891d912809fe484c2e745-Bj1m5e0s.woff2 +0 -0
- package/dist/client/_assets/chunks/6abdd163d2c4b85698db8aa1ce149361-B5FVGJuo.woff2 +0 -0
- package/dist/client/_assets/chunks/6b75213eb0be40ce84241eb2bb438a2e-CqHmF-NR.woff2 +0 -0
- package/dist/client/_assets/chunks/70714891ad3fbfc3d5f10a8669dacc5a-BWk5nLFN.woff2 +0 -0
- package/dist/client/_assets/chunks/7158022889c6c177062ac85036e7af10-B3D4K22n.woff2 +0 -0
- package/dist/client/_assets/chunks/73b794d885b88f7befb7ea8ab1780a15-D0ZehnEI.woff2 +0 -0
- package/dist/client/_assets/chunks/743f290baf027d4626a86e22f3d44600-DDTDQdRC.woff2 +0 -0
- package/dist/client/_assets/chunks/7520ca6ca8c60eb1e62d50e71c8d30f1-CVRaGu2G.woff2 +0 -0
- package/dist/client/_assets/chunks/757019c01c5e2c2b0f54293cea3b5636-BTovUnoH.woff2 +0 -0
- package/dist/client/_assets/chunks/759a647b77791b1e98f99bc0ab5317a7-CrK6FIFz.woff2 +0 -0
- package/dist/client/_assets/chunks/761d84afca8d7f34eebefe538adba827-DRbiVuwX.woff2 +0 -0
- package/dist/client/_assets/chunks/76e07530323418ec723c5d7634c9bcca-DGtVJTTH.woff2 +0 -0
- package/dist/client/_assets/chunks/76e51630143b95b6322ef93ad330da7a-D-kV82C_.woff2 +0 -0
- package/dist/client/_assets/chunks/76f117a3baacda304a71965a17266a13-DFfvsFUm.woff2 +0 -0
- package/dist/client/_assets/chunks/773819b7b9b8fd404a929867c0fd677e-Bu1xCMZb.woff2 +0 -0
- package/dist/client/_assets/chunks/77747f17625a259fb405b7bbdd84ac4b-o_vNfh-6.woff2 +0 -0
- package/dist/client/_assets/chunks/7817dd16805145d8538ad57590f69f5a-DguSUP3O.woff2 +0 -0
- package/dist/client/_assets/chunks/788498548ddfb710d6ef3c7bff79fa97-3ffoQfRd.woff2 +0 -0
- package/dist/client/_assets/chunks/78ea6c40923ce95367e3517dd1e5a849-DvLDRhX9.woff2 +0 -0
- package/dist/client/_assets/chunks/79aa7c8c842c4a27cf57c0a3a1ffca5a-sDoCeoKI.woff2 +0 -0
- package/dist/client/_assets/chunks/7b59f0ec7792b18458dc5a361e37884c-DWvxkKmT.woff2 +0 -0
- package/dist/client/_assets/chunks/7c3945788a689a69356c1a622d69d48b-1JjIpss2.woff2 +0 -0
- package/dist/client/_assets/chunks/7cc72fd2c9105560422b6a67c6162945-Co3AF5_G.woff2 +0 -0
- package/dist/client/_assets/chunks/7d0ea0690e432462f4d05a23d720ec19-Dx7DK4C3.woff2 +0 -0
- package/dist/client/_assets/chunks/7dffa5c1bec57e0903fd62357401ff1a-DuQHmiiN.woff2 +0 -0
- package/dist/client/_assets/chunks/7e38ca789a9e76ac91d9256374e154f0-BtETgGRe.woff2 +0 -0
- package/dist/client/_assets/chunks/7f9a6c9286b68de9c72b0024f7beeb40-JpXdo4FC.woff2 +0 -0
- package/dist/client/_assets/chunks/815a0b797465190f60d8b1a04656c42e-Spzo94nU.woff2 +0 -0
- package/dist/client/_assets/chunks/823d1bb11097331238d103c7f72138dc-CGN6m2VF.woff2 +0 -0
- package/dist/client/_assets/chunks/82f6ccc063960eed1cdfd1d61ada8862-CsmAqx2a.woff2 +0 -0
- package/dist/client/_assets/chunks/835b6505bb9eea9678925a1fa885353d-C6mSBwUc.woff2 +0 -0
- package/dist/client/_assets/chunks/845b4b67564d62cf5cad242f7ac08ea5-DnvzrloJ.woff2 +0 -0
- package/dist/client/_assets/chunks/84c8e7bc0931008ed91f44ed12dfea94-C2Hvhh-b.woff2 +0 -0
- package/dist/client/_assets/chunks/8726558cf297cbda831cc19514897205-gSFV1vW0.woff2 +0 -0
- package/dist/client/_assets/chunks/8855472f3c02f6c7ebb3216617ebe4af-C1JsWVqq.woff2 +0 -0
- package/dist/client/_assets/chunks/887a11290ba78b1e66c6d2f67043005e-L_A1f5Fz.woff2 +0 -0
- package/dist/client/_assets/chunks/888ba53510213d5d1f6a7deb60e569b6-OP8E-ELN.woff2 +0 -0
- package/dist/client/_assets/chunks/88db1d042074fb6e66821ffc10941930-kZ6K3e62.woff2 +0 -0
- package/dist/client/_assets/chunks/892aa49b2529c89bb4076d4aa51fe30f-Bw0_Km2A.woff2 +0 -0
- package/dist/client/_assets/chunks/8b5c9ef81159f31d2ab35f45ca4373e0-Djw95sKC.woff2 +0 -0
- package/dist/client/_assets/chunks/8b91c7c2ed390f1278b9befa3aa87233-B2C3iFbQ.woff2 +0 -0
- package/dist/client/_assets/chunks/8dc18c0cebe6aa4bf4c45dbb831048ab-bUlANLit.woff2 +0 -0
- package/dist/client/_assets/chunks/8e231d707f0c4f8e3cde90a6b52a79aa-CvwbWlul.woff2 +0 -0
- package/dist/client/_assets/chunks/8fb45117a62d92dce44a80f0b729ead5-Djr4-o5-.woff2 +0 -0
- package/dist/client/_assets/chunks/9064c12fd72c7aba235d8c3881755b91-BnguxT-p.woff2 +0 -0
- package/dist/client/_assets/chunks/90b7848e9b1623b77bdcf155e90b839c-CFvPRnoM.woff2 +0 -0
- package/dist/client/_assets/chunks/911b9e53e9814de2998c60bf3115f560-DmPmj635.woff2 +0 -0
- package/dist/client/_assets/chunks/919a879bd2d580d8491a31a449390689-BY2w-8_Y.woff2 +0 -0
- package/dist/client/_assets/chunks/91da6cb174bebfb96976e2f1316be2fe-DztAJb6W.woff2 +0 -0
- package/dist/client/_assets/chunks/92fdc376bce277874e75db666f175b44-DJUnHEIN.woff2 +0 -0
- package/dist/client/_assets/chunks/930a23430f0eb64480d7fe5f82834e21-2VrDPT7E.woff2 +0 -0
- package/dist/client/_assets/chunks/934dfdbed2bb2c4b6129199d699a34fa-CtpHsjRD.woff2 +0 -0
- package/dist/client/_assets/chunks/93bed0e5f2dd5a21cf73304fcfbc149d-DJiaETX7.woff2 +0 -0
- package/dist/client/_assets/chunks/93f3ea6918533d96d9c252378b9a4af0-D-FJaGi-.woff2 +0 -0
- package/dist/client/_assets/chunks/95a0951d2a2722ff773a1a45e8c474d6-DuZg-BpP.woff2 +0 -0
- package/dist/client/_assets/chunks/95ff41191c76ef893ac70a1043e95e44-C08QDbJO.woff2 +0 -0
- package/dist/client/_assets/chunks/96c75862c8cec51a7c05ff025fea86cd-DBnRwCuW.woff2 +0 -0
- package/dist/client/_assets/chunks/97d7a17234f2b5030ae9697ae00aded2-CkUSaB-p.woff2 +0 -0
- package/dist/client/_assets/chunks/982cd1765ca6bbb3e6547e47834d5148-DkIybx1r.woff2 +0 -0
- package/dist/client/_assets/chunks/985d4d41afa0934a4eb2de35473fb9a2-CF7rgJMw.woff2 +0 -0
- package/dist/client/_assets/chunks/995c4fda62d25c3b79dd5987737df6ae-CY5ri8HW.woff2 +0 -0
- package/dist/client/_assets/chunks/9b0c3caac458c7bc9c9133e40405ddea-DhPrYOcz.woff2 +0 -0
- package/dist/client/_assets/chunks/9b21e8244f5930c48ad5073f83777b6c-CuE9-r-r.woff2 +0 -0
- package/dist/client/_assets/chunks/9bb38fd05201de666b88b82d56a386f7-nMl6lB8X.woff2 +0 -0
- package/dist/client/_assets/chunks/9d0afa53dc2f92ba42024f55f11f6779-Bozpx8bL.woff2 +0 -0
- package/dist/client/_assets/chunks/9e13a47242926f1cd7d68c08d9ef8889-DwKQjc8M.woff2 +0 -0
- package/dist/client/_assets/chunks/9eff2ab2a9d2ced47ab761bf6fdb11ec-D86-Zm1H.woff2 +0 -0
- package/dist/client/_assets/chunks/9f1963fe8d4d6878d717d872a8f3fdb5-DxjITUFq.woff2 +0 -0
- package/dist/client/_assets/chunks/9f3bdab4025dc7c5ba1a5871e62e8918-BqxDEFLJ.woff2 +0 -0
- package/dist/client/_assets/chunks/9fce02d1a09f464c36c9fcfb14a354c5-F2tMAB_j.woff2 +0 -0
- package/dist/client/_assets/chunks/a00e94d4f04df6aa659db9c4954c7efe-CRBKCj_3.woff2 +0 -0
- package/dist/client/_assets/chunks/a0a6755c7e3f9e4cf03387027dd9f16c-BAyRqF2w.woff2 +0 -0
- package/dist/client/_assets/chunks/a0d00fe4816c95a8c7dffd445ef00b03-CxcTiRLJ.woff2 +0 -0
- package/dist/client/_assets/chunks/a0f199077fa1a33bc2a1e01c64de7fe6-IdgVJmZD.woff2 +0 -0
- package/dist/client/_assets/chunks/a2b0597837e382aca19919c4b74e58c8-7iVOBjzI.woff2 +0 -0
- package/dist/client/_assets/chunks/a373e85c96aa0dc303092429fb837e51-Dvmw8mbX.woff2 +0 -0
- package/dist/client/_assets/chunks/a379ada89abd750c587ded29f77e731c-CgPBsEsv.woff2 +0 -0
- package/dist/client/_assets/chunks/a4475c442c6b259354c9eda62e64e7df-TZ0c8vK-.woff2 +0 -0
- package/dist/client/_assets/chunks/a48be56ce5d3a99dc8f8331398004412-BR_ixH1W.woff2 +0 -0
- package/dist/client/_assets/chunks/a4a39b5f048671aa37c2b2a9581ab08a-EDfDYsUd.woff2 +0 -0
- package/dist/client/_assets/chunks/a534449d67561ac9b06489f64e57ad98-C355TCrD.woff2 +0 -0
- package/dist/client/_assets/chunks/a6e044424780a57610833cc856c15bf5-x1b8FE4H.woff2 +0 -0
- package/dist/client/_assets/chunks/a7c35e42a659347e490c3cb7983b2e7f-BWpA0-oz.woff2 +0 -0
- package/dist/client/_assets/chunks/a7c58070d68dc1724e9d4e781afb78db-ql27pT-6.woff2 +0 -0
- package/dist/client/_assets/chunks/a806759e72987951d6d08b7cbdf36a1b-TvLDwB_u.woff2 +0 -0
- package/dist/client/_assets/chunks/a88481ec8bc7be11cb66e46781a79bb9-Ck-r_sGx.woff2 +0 -0
- package/dist/client/_assets/chunks/a919a8c6fec6d7e8bb21177965f2e9d7-CC5uWzwd.woff2 +0 -0
- package/dist/client/_assets/chunks/a9a016d4a93409f65278327e8a8bb38d-dylrNwF8.woff2 +0 -0
- package/dist/client/_assets/chunks/aa662092cf249707123ca4d575e2764b-CSxNsTUL.woff2 +0 -0
- package/dist/client/_assets/chunks/aae1eda193285ab817a2d1b408440326-CjyjBoRm.woff2 +0 -0
- package/dist/client/_assets/chunks/ab92d41062a7644faa45f50bf384454a-Cq0X8a9H.woff2 +0 -0
- package/dist/client/_assets/chunks/accc43762ad05edfbff66ba1380d192a-B5jIykaZ.woff2 +0 -0
- package/dist/client/_assets/chunks/ad47bcd6d4663026d648c132d969318d-BYdIbDge.woff2 +0 -0
- package/dist/client/_assets/chunks/ad67113f88b59582991cb0c5d33ea19f-DOH1vcIZ.woff2 +0 -0
- package/dist/client/_assets/chunks/adb6e613fb99c6dd660b727101f554a8-DJDw_ZZK.woff2 +0 -0
- package/dist/client/_assets/chunks/adc4a98a89870ef984ee8f4b57c8a3a5-D30vw26R.woff2 +0 -0
- package/dist/client/_assets/chunks/af57a2e8c72f9ba0efb1af771d32c124-D5Zwq9pS.woff2 +0 -0
- package/dist/client/_assets/chunks/b0ae5f374e43dac992a4a5d334cebc0b-Ccoi4dPt.woff2 +0 -0
- package/dist/client/_assets/chunks/b0cb664cb2e1371efda8943c0b7dcd1c-YzGFnelF.woff2 +0 -0
- package/dist/client/_assets/chunks/b0d1cdced482352cf0d3ae58638aacb9-IunWfc6R.woff2 +0 -0
- package/dist/client/_assets/chunks/b12379ab782468d725519cd07a7d15bc-IjbydKs2.woff2 +0 -0
- package/dist/client/_assets/chunks/b320f5d185b2cff933ac549c184031c5-IYnPrIz4.woff2 +0 -0
- package/dist/client/_assets/chunks/b326a8a9c33b554db570da94f60bc380-B6dzaCQg.woff2 +0 -0
- package/dist/client/_assets/chunks/b55486f8a459c838fe329d4e79a8c211-DW8q7oGV.woff2 +0 -0
- package/dist/client/_assets/chunks/b6117ff2993b11bb1fdc7ea3588a010c-JoMYL5W_.woff2 +0 -0
- package/dist/client/_assets/chunks/b61982951bd51b724143c30dfaaa9fe9-D3KhBKl6.woff2 +0 -0
- package/dist/client/_assets/chunks/b91234c10fd8b8c8abc88e03afe66a1f-BABNh2la.woff2 +0 -0
- package/dist/client/_assets/chunks/b9317198f118a1dfd8ddf2b82ec028f3-CiMecxeH.woff2 +0 -0
- package/dist/client/_assets/chunks/ba825ae79b1a6f7e0cce5215fcb5c96f-B6EWU_D5.woff2 +0 -0
- package/dist/client/_assets/chunks/ba9c59d7dfa4494db1bb764ada81467d-D0yPcC1m.woff2 +0 -0
- package/dist/client/_assets/chunks/bab547459d514f46206e340c4bb2dc88-YnBqlGhw.woff2 +0 -0
- package/dist/client/_assets/chunks/bb0b15492b8cdbbec57c0bcfa0aa9241-BjIFbEsN.woff2 +0 -0
- package/dist/client/_assets/chunks/bc6183ac08f0fac78c46f80c10cf7c92-B81nbRb3.woff2 +0 -0
- package/dist/client/_assets/chunks/bc8ccea5abf6598cf3cfa97eb59804bb-B_7IbluG.woff2 +0 -0
- package/dist/client/_assets/chunks/bc95a792cbca6639214c9b0da13392ff-CHQe0E-D.woff2 +0 -0
- package/dist/client/_assets/chunks/bcc39eda837bb7a7a3d37c8c60fffb81-CGd1Zk6S.woff2 +0 -0
- package/dist/client/_assets/chunks/bcfa62f35731856246c146d3a6932bf3-DMDq6w4v.woff2 +0 -0
- package/dist/client/_assets/chunks/bd73264d7f98776708d5d6f3c9b78fcc-d9QWWMym.woff2 +0 -0
- package/dist/client/_assets/chunks/bed610b217d500f5975cfc9fe6157570-B5t7s1bw.woff2 +0 -0
- package/dist/client/_assets/chunks/bedc74b423b7293b6ad0bdecc61c42cc-4Nnh7peP.woff2 +0 -0
- package/dist/client/_assets/chunks/bf8901f8f11d4f433ea17dabc9370ea6-B8dkfvGn.woff2 +0 -0
- package/dist/client/_assets/chunks/bfb00d4a4c48661bd0be99f300a0faae-OxsWZ7l-.woff2 +0 -0
- package/dist/client/_assets/chunks/c0098958e20db68cab90097b5e62516f-VpjQo3vV.woff2 +0 -0
- package/dist/client/_assets/chunks/c063897793f593eb26d6ff7b7baaba18-BWeJJ1sH.woff2 +0 -0
- package/dist/client/_assets/chunks/c1b0df29ae41d764904df84e9ac83d1e-D1N934vs.woff2 +0 -0
- package/dist/client/_assets/chunks/c4143bb9f2fe77b6ccf20088a8904650-nHfYM24e.woff2 +0 -0
- package/dist/client/_assets/chunks/c42c67070cfe99cf823df92d81c7fa6e-B_a405Ez.woff2 +0 -0
- package/dist/client/_assets/chunks/c4bfaa5e50798246e3770718b7a7c84a-B-r6Y_o0.woff2 +0 -0
- package/dist/client/_assets/chunks/c4d749e45ecd5a5aed5a0bb3ebfd355d-CkqE1fRi.woff2 +0 -0
- package/dist/client/_assets/chunks/c6c2971ad1f3221f6cf84028aa0f477e-D7BZGIcY.woff2 +0 -0
- package/dist/client/_assets/chunks/c7ff3f6bbdcd5f604b7343602ab904df-Bq7UjVrZ.woff2 +0 -0
- package/dist/client/_assets/chunks/c84598999133455503042e06f4ab79cb-DIXndJX3.woff2 +0 -0
- package/dist/client/_assets/chunks/c945c62368357d05a53206620460fb30-82gFEf6n.woff2 +0 -0
- package/dist/client/_assets/chunks/c96d83978add28b356c22c4c84916733-BL4VaPIE.woff2 +0 -0
- package/dist/client/_assets/chunks/c97f41eef722121d86f55d553c056a39-CjejgQO2.woff2 +0 -0
- package/dist/client/_assets/chunks/ca62704509932d3232d62918de97af3f-CKWF-3mX.woff2 +0 -0
- package/dist/client/_assets/chunks/ca9a533988d7019597a60d4e17127e0c-ClZ6ygZM.woff2 +0 -0
- package/dist/client/_assets/chunks/cb7ccd6494256f7a2977b7c2b0225592-Cxp8XObm.woff2 +0 -0
- package/dist/client/_assets/chunks/ce022e18a1377ac509443c3c3790b431-DpH0t6Tn.woff2 +0 -0
- package/dist/client/_assets/chunks/ce41d70ce6a069a498525c9e15c45cf2-DGtgv-_b.woff2 +0 -0
- package/dist/client/_assets/chunks/cf2b28f90f47276f7e2688a65e88a101-LVbNkXHj.woff2 +0 -0
- package/dist/client/_assets/chunks/cf9cffe56636322f62b40d61130fbc5e-DNCBMqAo.woff2 +0 -0
- package/dist/client/_assets/chunks/d1f064825fa5784b5c930652bd831cce-BU6qwjp8.woff2 +0 -0
- package/dist/client/_assets/chunks/d20d8944bc0b85f5b2aae4b24f343516-oY9l-NmC.woff2 +0 -0
- package/dist/client/_assets/chunks/d2faabcedd19f016e7b21bce073c0ec8-Dv6eFCf5.woff2 +0 -0
- package/dist/client/_assets/chunks/d320171e57480510f87dbbc7d5264b0a-CcCfjaFX.woff2 +0 -0
- package/dist/client/_assets/chunks/d36644d6502527e1fff205d0c7eca434-kVBj8zy_.woff2 +0 -0
- package/dist/client/_assets/chunks/d3a72a99d365dddfbca8d017a8011368-Bgdmhl3M.woff2 +0 -0
- package/dist/client/_assets/chunks/d40ab99c7a38026f411c8f112f742b48-D7Nm7fg6.woff2 +0 -0
- package/dist/client/_assets/chunks/d42aafa0f246ad3b4c16fe96d3a3a432-CRxTqZXU.woff2 +0 -0
- package/dist/client/_assets/chunks/d4aaf23c13ae808a4bed617afd13aa07-d_T1SKe9.woff2 +0 -0
- package/dist/client/_assets/chunks/d509ee5c8241bc7c3de2039d75564fa5-BpNFD9JE.woff2 +0 -0
- package/dist/client/_assets/chunks/d675d717cd329bfd4c0524f76ae1579c-Bd5_ctoQ.woff2 +0 -0
- package/dist/client/_assets/chunks/d689b1861d7e4377dd72ad3013482612-jPsVOU3F.woff2 +0 -0
- package/dist/client/_assets/chunks/d6bb686bddfbe8f38a36a68e609a8667-C_QduS-X.woff2 +0 -0
- package/dist/client/_assets/chunks/d9020ff69a83b2a6ee1f42ae480f7db0-DLBUPMCe.woff2 +0 -0
- package/dist/client/_assets/chunks/d9047070d72a816b3dba9d40c2d85e69-LTQ42vEp.woff2 +0 -0
- package/dist/client/_assets/chunks/da04549f3f4ed28076b01b8cd710d313-CDXOeduw.woff2 +0 -0
- package/dist/client/_assets/chunks/da7af303f8c645f9a9dbae0e6e32dd35-DCkS2jqi.woff2 +0 -0
- package/dist/client/_assets/chunks/da90a9012ab2d98f759e3fa0820ef502-D0D9d1zg.woff2 +0 -0
- package/dist/client/_assets/chunks/dc6e234ded795e91f76d6647f628fbf0-IkooXcRh.woff2 +0 -0
- package/dist/client/_assets/chunks/dc8f6256445e68199540be9ade33529f-umb5tWbZ.woff2 +0 -0
- package/dist/client/_assets/chunks/dd0cfdfdac0866e66d587e2b5a9e9961-bl4ozE0c.woff2 +0 -0
- package/dist/client/_assets/chunks/de304c8a02e45ded4f8dcc479d167198-ClRu89-4.woff2 +0 -0
- package/dist/client/_assets/chunks/df10d94bea357a43313e20da5d84bd30-CmAgh2oA.woff2 +0 -0
- package/dist/client/_assets/chunks/df9568257eb29b156449fdd4bec5ec76-BMz6aLRr.woff2 +0 -0
- package/dist/client/_assets/chunks/e067cd0ed76c90cd0a93c9339253f20b-6RxCQ4jy.woff2 +0 -0
- package/dist/client/_assets/chunks/e08b07772e7bed3cec2832d43f7fd339-CQCkeKsr.woff2 +0 -0
- package/dist/client/_assets/chunks/e12150d5a39b30be8f567968c7a527b0-DjdoWzeQ.woff2 +0 -0
- package/dist/client/_assets/chunks/e34b2b141e472dc776c86fdf8eea23b0-Cjk7H-Ev.woff2 +0 -0
- package/dist/client/_assets/chunks/e444fd88f1390636e603d0d681538ac8-Bx7CPOZh.woff2 +0 -0
- package/dist/client/_assets/chunks/e53fcb2381eee345db4f6f973dd95a3e-DmkkCUWz.woff2 +0 -0
- package/dist/client/_assets/chunks/e5bd313ef81f687d398aacb11cec3069-CwBEbcji.woff2 +0 -0
- package/dist/client/_assets/chunks/e79898628283edc27180fc39d0d769c1-By2pplQz.woff2 +0 -0
- package/dist/client/_assets/chunks/e8c15be62faf978d208925e79ea6a10d-Ct5M9lTP.woff2 +0 -0
- package/dist/client/_assets/chunks/e8c364c16daa04835bf32d293d2598db-3v4Tut_d.woff2 +0 -0
- package/dist/client/_assets/chunks/e91c4d941ed9f5c2f3e27f205a3a225e-BwOJ2Lst.woff2 +0 -0
- package/dist/client/_assets/chunks/e9c566be7a5d38a9085225f7372bc82b-BCtnCVML.woff2 +0 -0
- package/dist/client/_assets/chunks/e9ebc567a711eeb29019ddae3e0ce7fe-CnNhV49O.woff2 +0 -0
- package/dist/client/_assets/chunks/eaf332445a40942928e5f5750c1c3116-qZ5LSRrj.woff2 +0 -0
- package/dist/client/_assets/chunks/eb5afb3d952b8593782caec6026514b6-BXugZF2T.woff2 +0 -0
- package/dist/client/_assets/chunks/ec86e23683052da5cfdc3b77641bd15a-BeLcbcRc.woff2 +0 -0
- package/dist/client/_assets/chunks/ecb8875a56c7b038b35432fda41ae128-BVqgNf1o.woff2 +0 -0
- package/dist/client/_assets/chunks/eda1b0cb6d1719dd9bedcf3216a9e8de-BJTfLL5z.woff2 +0 -0
- package/dist/client/_assets/chunks/edd6a4f608d04fc0351d7688cfc321e4-d3V4U40c.woff2 +0 -0
- package/dist/client/_assets/chunks/ee6fce20b420a480714607c66d7f97e5-DEtC-G5G.woff2 +0 -0
- package/dist/client/_assets/chunks/eee3836d6ac17ebb2c450bbcbc9db121-C5a2TZjQ.woff2 +0 -0
- package/dist/client/_assets/chunks/et-book-bold-line-figures-BFJr2_zv.woff +0 -0
- package/dist/client/_assets/chunks/et-book-display-italic-old-style-figures-CoeRJAe8.woff +0 -0
- package/dist/client/_assets/chunks/et-book-roman-line-figures-CaA40oOf.woff +0 -0
- package/dist/client/_assets/chunks/f1661731474b78bdf81114daf10b254f-KnMKX_U3.woff2 +0 -0
- package/dist/client/_assets/chunks/f2c58dee206ba9355046fc23d05491f7-Cm4tCWav.woff2 +0 -0
- package/dist/client/_assets/chunks/f50ac27ea4358d67fdda403c2bb52467-WCfYHde1.woff2 +0 -0
- package/dist/client/_assets/chunks/f55334112f8e8be82d65db29887a663f-vDq1zH8k.woff2 +0 -0
- package/dist/client/_assets/chunks/f5738255e92d8dd34a46d1bcdf4c4074-tpghTzL1.woff2 +0 -0
- package/dist/client/_assets/chunks/f740e93f3c0277ecc616594103bca683-C4BgW_ko.woff2 +0 -0
- package/dist/client/_assets/chunks/f7633b5af033d76ff2fb3c3c266d77c5-BCkrZxwO.woff2 +0 -0
- package/dist/client/_assets/chunks/f7d8468cba2335a83ee414ea68291bab-CVnotkZU.woff2 +0 -0
- package/dist/client/_assets/chunks/f91cd722855f4269256eae1187df64ec-DJlAx1A_.woff2 +0 -0
- package/dist/client/_assets/chunks/f9695c6c4df2bf6bc03045ff79d4f01f-DDaYGYHT.woff2 +0 -0
- package/dist/client/_assets/chunks/fa6e58ce4b52695e7ae19bbea6336ec8-B30jEpze.woff2 +0 -0
- package/dist/client/_assets/chunks/fada6eaa68ff8816afe43d2a36c5423e-DlGAWNPb.woff2 +0 -0
- package/dist/client/_assets/chunks/fbcc4bf5367218951172bdee6f77d7a6-Dax2VSCd.woff2 +0 -0
- package/dist/client/_assets/chunks/fc895f5ce66b656f4a933097bf2a8775-DjjysNdf.woff2 +0 -0
- package/dist/client/_assets/chunks/fc9ae5b600fb711f2d67e93ce768cba4-D1lx7LBz.woff2 +0 -0
- package/dist/client/_assets/chunks/fca6720fd14c467d29a90f18ef3859b9-B3btrwq3.woff2 +0 -0
- package/dist/client/_assets/chunks/fd1bb507bcbf04856eeb7f7fd47ea579-NR1nGJTt.woff2 +0 -0
- package/dist/client/_assets/chunks/fd1bceb55d3e0183ac2454b8532fec7d-D3t-v6nn.woff2 +0 -0
- package/dist/client/_assets/chunks/fd2069e6e8588c70b4f11364093b81f2-BoXhYVgw.woff2 +0 -0
- package/dist/client/_assets/chunks/{url-CG0eolsk.js → url-pLre2DM_.js} +1 -1
- package/dist/client/_assets/client-C_kImWZj.css +2 -0
- package/dist/client/_assets/client-D95FNDg5.js +272 -0
- package/dist/client/_assets/client-auth-CXILhW1b.js +4547 -0
- package/dist/client/_assets/client-cjk-jp-DZwrTzQC.css +1 -0
- package/dist/client/_assets/client-cjk-kr-_3ZNI2ZP.css +1 -0
- package/dist/env-CgaH9Mut.js +252 -0
- package/dist/github-api-BkRWnqMx.js +176 -0
- package/dist/github-app-WeadXMb8.js +275 -0
- package/dist/github-sync-7y_nTXx1.js +4723 -0
- package/dist/index.js +88 -2
- package/dist/node.js +71 -30
- package/dist/url-umUptr5z.js +334 -0
- package/package.json +16 -12
- package/src/__tests__/bin/content-cli.test.ts +179 -0
- package/src/__tests__/bin/media-cli.test.ts +192 -0
- package/src/__tests__/export-hugo-build.test.ts +178 -0
- package/src/__tests__/export-import-roundtrip.test.ts +265 -0
- package/src/__tests__/export-service.test.ts +763 -510
- package/src/__tests__/helpers/app.ts +15 -4
- package/src/__tests__/helpers/export-fixtures.ts +108 -0
- package/src/__tests__/import-site-command.test.ts +358 -350
- package/src/__tests__/site-pull-media.test.ts +256 -0
- package/src/app.tsx +68 -13
- package/src/client/__tests__/collection-form-bridge.test.ts +87 -7
- package/src/client/__tests__/collection-page-actions.test.ts +4 -4
- package/src/client/__tests__/collection-picker-order.test.ts +53 -0
- package/src/client/__tests__/collection-sort-menu.test.ts +2 -2
- package/src/client/__tests__/compose-bridge.test.ts +305 -13
- package/src/client/__tests__/compose-launch.test.ts +120 -0
- package/src/client/__tests__/compose-shortcuts.test.ts +141 -0
- package/src/client/__tests__/feed-video-player.test.ts +44 -0
- package/src/client/__tests__/site-header-nav.test.ts +250 -0
- package/src/client/__tests__/sortable-list.test.ts +6 -2
- package/src/client/__tests__/thread-context.test.ts +117 -0
- package/src/client/collection-form-bridge.ts +109 -4
- package/src/client/collection-page-actions.ts +3 -1
- package/src/client/collection-picker-order.ts +104 -0
- package/src/client/components/__tests__/{jant-collection-sidebar.test.ts → jant-collection-directory.test.ts} +91 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +40 -18
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1285 -66
- package/src/client/components/__tests__/jant-compose-editor.test.ts +254 -19
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +127 -1
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +237 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +326 -0
- package/src/client/components/__tests__/jant-post-menu.test.ts +372 -2
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +61 -5
- package/src/client/components/__tests__/jant-settings-general.test.ts +29 -28
- package/src/client/components/__tests__/jant-text-preview.test.ts +206 -0
- package/src/client/components/collection-manager-types.ts +18 -1
- package/src/client/components/compose-types.ts +48 -0
- package/src/client/components/jant-collection-directory.ts +1448 -0
- package/src/client/components/jant-collection-form.ts +65 -17
- package/src/client/components/jant-command-palette.ts +544 -0
- package/src/client/components/jant-compose-dialog.ts +2490 -375
- package/src/client/components/jant-compose-editor.ts +420 -75
- package/src/client/components/jant-compose-fullscreen.ts +59 -8
- package/src/client/components/jant-media-lightbox.ts +373 -18
- package/src/client/components/jant-nav-manager.ts +624 -286
- package/src/client/components/jant-post-menu.ts +451 -126
- package/src/client/components/jant-repo-picker-types.ts +39 -0
- package/src/client/components/jant-repo-picker.ts +799 -0
- package/src/client/components/jant-settings-avatar.ts +16 -2
- package/src/client/components/jant-settings-general.ts +137 -35
- package/src/client/components/jant-text-preview.ts +239 -55
- package/src/client/components/nav-manager-types.ts +25 -9
- package/src/client/components/settings-types.ts +13 -2
- package/src/client/compose-bridge.ts +428 -68
- package/src/client/compose-launch.ts +41 -4
- package/src/client/compose-shortcuts.ts +77 -5
- package/src/client/feed-video-player.ts +374 -0
- package/src/client/image-processor.ts +53 -127
- package/src/client/media-lightbox-events.ts +1 -0
- package/src/client/media-scroll-hint.ts +66 -0
- package/src/client/multipart-upload.ts +4 -1
- package/src/client/palette-shortcuts.ts +35 -0
- package/src/client/search-rank.ts +74 -0
- package/src/client/settings-bridge.ts +7 -0
- package/src/client/site-header-nav.d.ts +1 -0
- package/src/client/site-header-nav.js +139 -51
- package/src/client/sortable-list.ts +2 -0
- package/src/client/thread-context.ts +54 -11
- package/src/client/tiptap/__tests__/block-insertion.test.ts +65 -0
- package/src/client/tiptap/__tests__/footnotes.test.ts +692 -0
- package/src/client/tiptap/__tests__/insert-paragraph-around.test.ts +228 -0
- package/src/client/tiptap/__tests__/link-toolbar.test.ts +120 -4
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +48 -0
- package/src/client/tiptap/block-insertion.ts +36 -0
- package/src/client/tiptap/bubble-menu.ts +34 -4
- package/src/client/tiptap/create-editor.ts +49 -30
- package/src/client/tiptap/embed-dialog.ts +301 -0
- package/src/client/tiptap/embed-node.ts +373 -0
- package/src/client/tiptap/embed-paste.ts +91 -0
- package/src/client/tiptap/extensions.ts +80 -28
- package/src/client/tiptap/footnotes.ts +718 -0
- package/src/client/tiptap/html-block-node.ts +222 -0
- package/src/client/tiptap/image-node.ts +49 -6
- package/src/client/tiptap/inline-image-upload.ts +200 -40
- package/src/client/tiptap/insert-paragraph-around.ts +79 -0
- package/src/client/tiptap/link-input-rules.ts +50 -16
- package/src/client/tiptap/link-toolbar.ts +156 -133
- package/src/client/tiptap/markdown-clipboard.ts +120 -0
- package/src/client/tiptap/more-break.ts +36 -3
- package/src/client/tiptap/paste-media.ts +6 -0
- package/src/client/tiptap/slash-commands.ts +167 -14
- package/src/client/tiptap/tab-indent.ts +184 -0
- package/src/client/tiptap/toolbar-mode.ts +15 -2
- package/src/client/tiptap/wrapping-input-rules.ts +102 -0
- package/src/client/types/sortablejs.d.ts +9 -1
- package/src/client/upload-session.ts +2 -1
- package/src/client/video-processor.ts +177 -20
- package/src/client-auth.ts +9 -1
- package/src/client-site.ts +15 -0
- package/src/client.ts +2 -0
- package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
- package/src/db/__tests__/migration-rehearsal.test.ts +5 -1
- package/src/db/__tests__/migrations.test.ts +25 -2
- package/src/db/backfills/0001_strip_collection_path_c_prefix.sql +11 -0
- package/src/db/backfills/0002_strip_collection_path_c_prefix_fix.sql +12 -0
- package/src/db/backfills/0003_clear_system_nav_default_labels.sql +15 -0
- package/src/db/dialect.ts +40 -0
- package/src/db/migrations/0005_busy_the_phantom.sql +39 -0
- package/src/db/migrations/0006_adorable_magdalene.sql +3 -0
- package/src/db/migrations/0007_unusual_warstar.sql +1 -0
- package/src/db/migrations/0008_nasty_lockheed.sql +1 -0
- package/src/db/migrations/0009_clear_fixer.sql +31 -0
- package/src/db/migrations/0010_futuristic_preak.sql +31 -0
- package/src/db/migrations/0011_bizarre_smasher.sql +30 -0
- package/src/db/migrations/0012_furry_thena.sql +52 -0
- package/src/db/migrations/0013_mixed_lightspeed.sql +39 -0
- package/src/db/migrations/0014_high_the_santerians.sql +41 -0
- package/src/db/migrations/0015_skinny_shinobi_shaw.sql +1 -0
- package/src/db/migrations/0016_remarkable_nicolaos.sql +17 -0
- package/src/db/migrations/0017_powerful_moonstone.sql +14 -0
- package/src/db/migrations/0018_yummy_franklin_richards.sql +6 -0
- package/src/db/migrations/meta/0005_snapshot.json +1904 -0
- package/src/db/migrations/meta/0006_snapshot.json +1925 -0
- package/src/db/migrations/meta/0007_snapshot.json +1933 -0
- package/src/db/migrations/meta/0008_snapshot.json +1940 -0
- package/src/db/migrations/meta/0009_snapshot.json +1952 -0
- package/src/db/migrations/meta/0010_snapshot.json +1952 -0
- package/src/db/migrations/meta/0011_snapshot.json +1948 -0
- package/src/db/migrations/meta/0012_snapshot.json +1955 -0
- package/src/db/migrations/meta/0013_snapshot.json +1977 -0
- package/src/db/migrations/meta/0014_snapshot.json +1988 -0
- package/src/db/migrations/meta/0015_snapshot.json +1995 -0
- package/src/db/migrations/meta/0016_snapshot.json +2104 -0
- package/src/db/migrations/meta/0017_snapshot.json +2188 -0
- package/src/db/migrations/meta/0018_snapshot.json +2225 -0
- package/src/db/migrations/meta/_journal.json +98 -0
- package/src/db/migrations/pg/0003_motionless_norrin_radd.sql +21 -0
- package/src/db/migrations/pg/0004_nervous_captain_midlands.sql +3 -0
- package/src/db/migrations/pg/0005_romantic_mesmero.sql +1 -0
- package/src/db/migrations/pg/0006_perpetual_bruce_banner.sql +1 -0
- package/src/db/migrations/pg/0007_nav_item_placement.sql +1 -0
- package/src/db/migrations/pg/0008_yielding_frightful_four.sql +3 -0
- package/src/db/migrations/pg/0009_outstanding_ogun.sql +1 -0
- package/src/db/migrations/pg/0010_overjoyed_gertrude_yorkes.sql +28 -0
- package/src/db/migrations/pg/0011_fixed_hulk.sql +19 -0
- package/src/db/migrations/pg/0012_cute_shockwave.sql +2 -0
- package/src/db/migrations/pg/0013_bizarre_obadiah_stane.sql +1 -0
- package/src/db/migrations/pg/0014_tearful_grim_reaper.sql +16 -0
- package/src/db/migrations/pg/0015_daffy_mikhail_rasputin.sql +14 -0
- package/src/db/migrations/pg/0016_familiar_lionheart.sql +6 -0
- package/src/db/migrations/pg/meta/0003_snapshot.json +2482 -0
- package/src/db/migrations/pg/meta/0004_snapshot.json +2500 -0
- package/src/db/migrations/pg/meta/0005_snapshot.json +2507 -0
- package/src/db/migrations/pg/meta/0006_snapshot.json +2513 -0
- package/src/db/migrations/pg/meta/0008_snapshot.json +2524 -0
- package/src/db/migrations/pg/meta/0009_snapshot.json +2520 -0
- package/src/db/migrations/pg/meta/0010_snapshot.json +2526 -0
- package/src/db/migrations/pg/meta/0011_snapshot.json +2563 -0
- package/src/db/migrations/pg/meta/0012_snapshot.json +2573 -0
- package/src/db/migrations/pg/meta/0013_snapshot.json +2579 -0
- package/src/db/migrations/pg/meta/0014_snapshot.json +2702 -0
- package/src/db/migrations/pg/meta/0015_snapshot.json +2803 -0
- package/src/db/migrations/pg/meta/0016_snapshot.json +2840 -0
- package/src/db/migrations/pg/meta/_journal.json +98 -0
- package/src/db/pg/__tests__/node.test.ts +38 -0
- package/src/db/pg/node.ts +149 -0
- package/src/db/pg/schema.ts +148 -12
- package/src/db/rehearsal-fixtures/demo-current.sql +47 -3
- package/src/db/schema.ts +162 -12
- package/src/i18n/Trans.tsx +3 -3
- package/src/i18n/__tests__/context.test.tsx +9 -6
- package/src/i18n/__tests__/detect.test.ts +65 -48
- package/src/i18n/__tests__/fallback.test.ts +40 -0
- package/src/i18n/__tests__/message-placeholders.test.ts +43 -0
- package/src/i18n/__tests__/middleware.test.ts +83 -0
- package/src/i18n/context.tsx +9 -44
- package/src/i18n/detect.ts +95 -28
- package/src/i18n/i18n.ts +41 -17
- package/src/i18n/index.ts +7 -5
- package/src/i18n/locales/{en.po → public/en.po} +284 -1151
- package/src/i18n/locales/public/en.ts +1 -0
- package/src/i18n/locales/public/zh-Hans.po +2327 -0
- package/src/i18n/locales/public/zh-Hans.ts +1 -0
- package/src/i18n/locales/public/zh-Hant.po +2327 -0
- package/src/i18n/locales/public/zh-Hant.ts +1 -0
- package/src/i18n/locales/settings/en.po +1622 -0
- package/src/i18n/locales/settings/en.ts +1 -0
- package/src/i18n/locales/settings/zh-Hans.po +1622 -0
- package/src/i18n/locales/settings/zh-Hans.ts +1 -0
- package/src/i18n/locales/settings/zh-Hant.po +1622 -0
- package/src/i18n/locales/settings/zh-Hant.ts +1 -0
- package/src/i18n/middleware.ts +37 -24
- package/src/index.ts +4 -6
- package/src/lib/__tests__/collection-groups.test.ts +17 -0
- package/src/lib/__tests__/constants.test.ts +2 -24
- package/src/lib/__tests__/csp-builder.test.ts +68 -0
- package/src/lib/__tests__/display-text.test.ts +27 -0
- package/src/lib/__tests__/embed-providers.test.ts +104 -0
- package/src/lib/__tests__/embed-render.test.ts +60 -0
- package/src/lib/__tests__/feed.test.ts +125 -63
- package/src/lib/__tests__/hosted-signin.test.ts +41 -2
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +88 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +59 -0
- package/src/lib/__tests__/markdown.test.ts +47 -5
- package/src/lib/__tests__/navigation.test.ts +132 -0
- package/src/lib/__tests__/post-display.test.ts +3 -2
- package/src/lib/__tests__/post-meta.test.ts +3 -0
- package/src/lib/__tests__/rate-limit-d1.test.ts +82 -0
- package/src/lib/__tests__/rate-limit-memory.test.ts +69 -0
- package/src/lib/__tests__/resolve-config.test.ts +30 -25
- package/src/lib/__tests__/schemas.test.ts +20 -3
- package/src/lib/__tests__/summary.test.ts +540 -1
- package/src/lib/__tests__/theme.test.ts +41 -2
- package/src/lib/__tests__/timeline.test.ts +33 -27
- package/src/lib/__tests__/tiptap-render.test.ts +224 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +51 -0
- package/src/lib/__tests__/video-playback.test.ts +48 -0
- package/src/lib/__tests__/view.test.ts +181 -8
- package/src/lib/__tests__/worker-response-cache.test.ts +311 -0
- package/src/lib/__tests__/youtube.test.ts +99 -0
- package/src/lib/api-media.ts +55 -0
- package/src/lib/api-posts.ts +99 -0
- package/src/lib/api-search.ts +47 -0
- package/src/lib/api-settings.ts +122 -0
- package/src/lib/asset-path.ts +20 -2
- package/src/lib/collection-groups.ts +2 -1
- package/src/lib/collection-paths.ts +40 -0
- package/src/lib/constants.ts +3 -24
- package/src/lib/csp-builder.ts +99 -0
- package/src/lib/decorative-quote-mark.ts +11 -0
- package/src/lib/display-text.ts +56 -0
- package/src/lib/embed-providers.ts +289 -0
- package/src/lib/embed-render.ts +151 -0
- package/src/lib/env.ts +70 -1
- package/src/lib/excerpt.ts +11 -2
- package/src/lib/feed.ts +154 -95
- package/src/lib/footnotes.ts +146 -0
- package/src/lib/github-api.ts +423 -0
- package/src/lib/github-app-state.ts +135 -0
- package/src/lib/github-app.ts +487 -0
- package/src/lib/github-sync-queue-handler.ts +69 -0
- package/src/lib/github-sync-site-config.ts +57 -0
- package/src/lib/github-sync-status.tsx +87 -0
- package/src/lib/github-sync-trigger.ts +199 -0
- package/src/lib/github-sync-worker.ts +72 -0
- package/src/lib/hosted-signin.ts +9 -3
- package/src/lib/hugo-markdown.ts +255 -0
- package/src/lib/job-queue-cf.ts +18 -0
- package/src/lib/job-queue-db.ts +149 -0
- package/src/lib/job-queue.ts +35 -0
- package/src/lib/markdown-manager.ts +864 -0
- package/src/lib/markdown-to-tiptap.ts +4 -323
- package/src/lib/markdown.ts +16 -24
- package/src/lib/media-helpers.ts +1 -0
- package/src/lib/navigation.ts +50 -19
- package/src/lib/post-display.ts +10 -22
- package/src/lib/post-meta.ts +20 -2
- package/src/lib/public-storage.ts +6 -1
- package/src/lib/rate-limit-d1.ts +99 -0
- package/src/lib/rate-limit-memory.ts +105 -0
- package/src/lib/rate-limit.ts +63 -0
- package/src/lib/render.tsx +14 -2
- package/src/lib/resolve-config.ts +32 -11
- package/src/lib/rich-image.ts +89 -0
- package/src/lib/schemas.ts +180 -13
- package/src/lib/summary.ts +162 -20
- package/src/lib/theme.ts +119 -0
- package/src/lib/timeline.ts +63 -42
- package/src/lib/tiptap-render.ts +265 -126
- package/src/lib/tiptap-to-markdown.ts +6 -329
- package/src/lib/upload.ts +97 -6
- package/src/lib/url.ts +34 -0
- package/src/lib/version.ts +18 -0
- package/src/lib/video-playback.ts +73 -0
- package/src/lib/view.ts +168 -94
- package/src/lib/webhook-signature.ts +65 -0
- package/src/lib/worker-response-cache.ts +220 -0
- package/src/lib/youtube.ts +119 -0
- package/src/middleware/__tests__/auth.test.ts +44 -4
- package/src/middleware/__tests__/onboarding.test.ts +3 -3
- package/src/middleware/__tests__/rate-limit.test.ts +113 -0
- package/src/middleware/__tests__/secure-headers.test.ts +28 -1
- package/src/middleware/__tests__/session.test.ts +85 -0
- package/src/middleware/auth.ts +62 -25
- package/src/middleware/config.ts +7 -10
- package/src/middleware/cors.ts +49 -0
- package/src/middleware/rate-limit.ts +54 -0
- package/src/middleware/secure-headers.ts +45 -55
- package/src/middleware/session.ts +36 -0
- package/src/node/__tests__/cli-migrate.test.ts +111 -0
- package/src/node/__tests__/cli-runtime-target.test.ts +1 -1
- package/src/node/__tests__/cli-site-token-env.test.ts +2 -2
- package/src/preset.css +316 -12
- package/src/routes/__tests__/compose.test.ts +4 -2
- package/src/routes/api/__tests__/collections.test.ts +81 -27
- package/src/routes/api/__tests__/mcp.test.ts +389 -0
- package/src/routes/api/__tests__/nav-items.test.ts +163 -2
- package/src/routes/api/__tests__/posts.test.ts +190 -0
- package/src/routes/api/__tests__/search.test.ts +48 -0
- package/src/routes/api/__tests__/settings.test.ts +99 -3
- package/src/routes/api/__tests__/upload-multipart.test.ts +21 -6
- package/src/routes/api/__tests__/upload.test.ts +132 -0
- package/src/routes/api/__tests__/uploads.test.ts +5 -3
- package/src/routes/api/collections.ts +67 -26
- package/src/routes/api/custom-urls.ts +1 -0
- package/src/routes/api/export.ts +8 -4
- package/src/routes/api/github-sync.tsx +380 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +45 -2
- package/src/routes/api/internal/search-reindex.ts +40 -0
- package/src/routes/api/internal/sites.ts +2 -0
- package/src/routes/api/internal/text-attachments.ts +60 -0
- package/src/routes/api/internal/uploads.ts +5 -1
- package/src/routes/api/mcp.ts +30 -0
- package/src/routes/api/nav-items.ts +30 -11
- package/src/routes/api/palette.ts +17 -0
- package/src/routes/api/posts.ts +37 -106
- package/src/routes/api/public/__tests__/posts.test.ts +372 -0
- package/src/routes/api/public/posts.ts +302 -0
- package/src/routes/api/search.ts +13 -46
- package/src/routes/api/settings.ts +44 -58
- package/src/routes/api/upload-multipart.ts +15 -5
- package/src/routes/api/upload.ts +62 -49
- package/src/routes/api/uploads.ts +1 -0
- package/src/routes/auth/__tests__/hosted-sso.test.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +29 -4
- package/src/routes/auth/dev.ts +1 -1
- package/src/routes/auth/hosted-sso-expired-page.tsx +89 -0
- package/src/routes/auth/hosted-sso.ts +42 -1
- package/src/routes/auth/reset.tsx +46 -32
- package/src/routes/auth/setup.tsx +53 -43
- package/src/routes/auth/signin.tsx +57 -29
- package/src/routes/compose.tsx +114 -1
- package/src/routes/dash/__tests__/font-theme.test.ts +6 -27
- package/src/routes/dash/__tests__/settings-avatar.test.ts +2 -3
- package/src/routes/dash/custom-urls.tsx +250 -112
- package/src/routes/dash/settings.tsx +1182 -75
- package/src/routes/feed/__tests__/{rss.test.ts → feed.test.ts} +62 -123
- package/src/routes/feed/__tests__/sitemap.test.ts +320 -4
- package/src/routes/feed/feed.ts +386 -0
- package/src/routes/feed/manifest.ts +67 -0
- package/src/routes/feed/sitemap.ts +211 -27
- package/src/routes/pages/__tests__/collection-routing.test.ts +176 -0
- package/src/routes/pages/__tests__/collections.test.ts +2 -3
- package/src/routes/pages/__tests__/featured.test.ts +2 -3
- package/src/routes/pages/__tests__/page-canonical.test.ts +101 -0
- package/src/routes/pages/archive.tsx +523 -127
- package/src/routes/pages/collection.tsx +197 -43
- package/src/routes/pages/collections.tsx +5 -1
- package/src/routes/pages/home.tsx +26 -17
- package/src/routes/pages/latest.tsx +3 -3
- package/src/routes/pages/page.tsx +255 -3
- package/src/routes/pages/partials.tsx +8 -14
- package/src/routes/pages/search.tsx +17 -1
- package/src/routes/pages/theme-sample.tsx +1 -2
- package/src/runtime/__tests__/node.test.ts +1 -1
- package/src/runtime/cloudflare.ts +4 -0
- package/src/runtime/node.ts +16 -0
- package/src/runtime/readiness.ts +1 -1
- package/src/runtime/site.ts +9 -1
- package/src/services/__tests__/collection.test.ts +137 -54
- package/src/services/__tests__/github-app-installations.test.ts +181 -0
- package/src/services/__tests__/github-sync-classify.test.ts +189 -0
- package/src/services/__tests__/github-sync-push.test.ts +159 -0
- package/src/services/__tests__/media.test.ts +444 -7
- package/src/services/__tests__/navigation.test.ts +271 -14
- package/src/services/__tests__/post-timeline.test.ts +31 -21
- package/src/services/__tests__/post.test.ts +288 -9
- package/src/services/__tests__/search.test.ts +44 -0
- package/src/services/__tests__/settings.test.ts +43 -21
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +10 -7
- package/src/services/collection.ts +591 -193
- package/src/services/custom-url.ts +34 -7
- package/src/services/export-theme/assets/client-site.css +2 -0
- package/src/services/export-theme/assets/client-site.js +145 -0
- package/src/services/export-theme/layouts/_default/alias.html +49 -0
- package/src/services/export-theme/layouts/_default/baseof.html +21 -0
- package/src/services/export-theme/layouts/_default/list.html +104 -0
- package/src/services/export-theme/layouts/_default/rss.xml +160 -0
- package/src/services/export-theme/layouts/_default/single.html +15 -0
- package/src/services/export-theme/layouts/archive/list.html +31 -0
- package/src/services/export-theme/layouts/collection/single.html +19 -0
- package/src/services/export-theme/layouts/collections/list.html +111 -0
- package/src/services/export-theme/layouts/featured/list.html +28 -0
- package/src/services/export-theme/layouts/index.html +41 -0
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +107 -0
- package/src/services/export-theme/layouts/partials/footer.html +32 -0
- package/src/services/export-theme/layouts/partials/head.html +52 -0
- package/src/services/export-theme/layouts/partials/header.html +115 -0
- package/src/services/export-theme/layouts/partials/media-gallery.html +246 -0
- package/src/services/export-theme/layouts/partials/pagination.html +55 -0
- package/src/services/export-theme/layouts/partials/post-card.html +127 -0
- package/src/services/export-theme/layouts/partials/reply.html +91 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +82 -0
- package/src/services/export-theme/layouts/post/list.html +129 -0
- package/src/services/export-theme/styles/main.css +1981 -0
- package/src/services/export-theme/theme.toml +12 -0
- package/src/services/export.ts +1372 -2412
- package/src/services/github-app-installations.ts +302 -0
- package/src/services/github-sync.ts +769 -0
- package/src/services/index.ts +15 -0
- package/src/services/mcp.ts +1324 -0
- package/src/services/media.ts +336 -25
- package/src/services/navigation.ts +115 -23
- package/src/services/path.ts +129 -28
- package/src/services/post.ts +858 -197
- package/src/services/search.ts +3 -0
- package/src/services/settings.ts +29 -15
- package/src/services/site-admin.ts +26 -6
- package/src/services/upload-session.ts +69 -34
- package/src/style-cjk-jp.css +3 -0
- package/src/style-cjk-kr.css +3 -0
- package/src/styles/components.css +160 -59
- package/src/styles/fonts/et-book/et-book-bold-line-figures.woff +0 -0
- package/src/styles/fonts/et-book/et-book-display-italic-old-style-figures.woff +0 -0
- package/src/styles/fonts/et-book/et-book-roman-line-figures.woff +0 -0
- package/src/styles/fonts/et-book.css +31 -0
- package/src/styles/fonts/latin.css +1 -0
- package/src/styles/fonts/noto-serif-jp/400/057a6a98bda7fe57105ddaa99ec82015.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0acd1fe2b2ea1ad1bfee7ae1fa139d27.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0c2ab6b295e55f356f8020d4e7747522.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0c5f9492af03a4fa42c784de94649de1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0d9a936885a4c39077438effd3779cbd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0e645da524f7cfc0e8c3c03fb2b08428.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/135e83b403475c5dc9e49b43501a5b84.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/13b2c53b30e6a3e4de7132dbc18dbdfc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/15d95680dc31cc6ce20e0a5464106dc4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/1733f27476507ca68b68a803bc533cc2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/188c2db794f3dd7a45889ddbc81da9bb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/1cf737900dd49c2e88f1b3221a82a602.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/233d3a685ee18276b319363474599d47.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/284b53bbefb06924cf236d24c8ed5641.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2a56eaf19d1d38a6b57e2a388f733676.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2abfbab82b6a7c04426afc054d2464da.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2e6f4bb71ef6b38765d51acc5a79f638.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/31194d303a67561926a544ed0e072aee.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/3623d96698132c61e861f984ce11cc23.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/39211b02f24c69cbd1b54f6d41b01933.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/3e24063b19abd2b051af986a6870046d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/4410f876d27c9821839474b6b1059fd7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/479329a39a3eb6c0047e6b0981919855.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/527ff336279465617aafbafe1a830632.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/56b40518ea0608e62826bd44ee27f5f9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5acc7f736217259db79a42bf44241c48.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5c05f8b75c9d7f25b3c04ca711cf43b3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5e0124c7265f1b4cf0fc797b94efbebc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/615a7e0d63f257109d599b2a1977de68.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/61a2c80d0c924d5a6187c02e8d1d1642.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/62fc94f8d4c5a750b7f25e7387539910.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/639c7c6b0b0c27c738702741cfa4b8c0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/68a3a4bf337f8a0722be76676e20b850.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/6abdd163d2c4b85698db8aa1ce149361.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/73b794d885b88f7befb7ea8ab1780a15.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/743f290baf027d4626a86e22f3d44600.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/757019c01c5e2c2b0f54293cea3b5636.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/76f117a3baacda304a71965a17266a13.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/77747f17625a259fb405b7bbdd84ac4b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/788498548ddfb710d6ef3c7bff79fa97.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/78ea6c40923ce95367e3517dd1e5a849.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/7f9a6c9286b68de9c72b0024f7beeb40.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/823d1bb11097331238d103c7f72138dc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/888ba53510213d5d1f6a7deb60e569b6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/892aa49b2529c89bb4076d4aa51fe30f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/95ff41191c76ef893ac70a1043e95e44.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/96c75862c8cec51a7c05ff025fea86cd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/9b21e8244f5930c48ad5073f83777b6c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/9fce02d1a09f464c36c9fcfb14a354c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a0a6755c7e3f9e4cf03387027dd9f16c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a4475c442c6b259354c9eda62e64e7df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a4a39b5f048671aa37c2b2a9581ab08a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a534449d67561ac9b06489f64e57ad98.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a919a8c6fec6d7e8bb21177965f2e9d7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ad67113f88b59582991cb0c5d33ea19f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/adb6e613fb99c6dd660b727101f554a8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/adc4a98a89870ef984ee8f4b57c8a3a5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/b12379ab782468d725519cd07a7d15bc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/b55486f8a459c838fe329d4e79a8c211.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bb0b15492b8cdbbec57c0bcfa0aa9241.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bedc74b423b7293b6ad0bdecc61c42cc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bfb00d4a4c48661bd0be99f300a0faae.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c1b0df29ae41d764904df84e9ac83d1e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c42c67070cfe99cf823df92d81c7fa6e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c4d749e45ecd5a5aed5a0bb3ebfd355d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c96d83978add28b356c22c4c84916733.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/cb7ccd6494256f7a2977b7c2b0225592.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ce41d70ce6a069a498525c9e15c45cf2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/d509ee5c8241bc7c3de2039d75564fa5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/d675d717cd329bfd4c0524f76ae1579c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/dc8f6256445e68199540be9ade33529f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e8c15be62faf978d208925e79ea6a10d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e91c4d941ed9f5c2f3e27f205a3a225e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e9c566be7a5d38a9085225f7372bc82b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e9ebc567a711eeb29019ddae3e0ce7fe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ecb8875a56c7b038b35432fda41ae128.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f1661731474b78bdf81114daf10b254f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f2c58dee206ba9355046fc23d05491f7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f50ac27ea4358d67fdda403c2bb52467.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f55334112f8e8be82d65db29887a663f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f740e93f3c0277ecc616594103bca683.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f7633b5af033d76ff2fb3c3c266d77c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f7d8468cba2335a83ee414ea68291bab.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/fc9ae5b600fb711f2d67e93ce768cba4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/fd1bceb55d3e0183ac2454b8532fec7d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/02611a045a7fe83a12014e3debc9f731.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/0f5e1a18987dbc84ca05188c129e1936.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/112743a4ab5fdd1498dfdf2b11336380.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1211f03d3ff5759a702631445793f60e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/147b24c2f08d03bbed30887d4cb3311c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/16663e567f81f4725a1522f37e18f71f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1824321da801e6257b902f3d1d09054c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1a3a92c7c060a71a6c35819b9ebcdbb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1fba7ec0e412e911bf31841de5a8a4e4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/28a97af9ab9d38983d20f39ff09840e1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2bfaadaf3479c72286248e6de0be0ec9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2c8c55e4cec85ce77e95cac9d330f5cf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2ed9981d2e8983365bd051159b976b6d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2f4c633e923ba30c6ba376367379fc91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/349965eee0a9b6c984a319ab96a4ece9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/36cda3eae13370b934bba4429fab9078.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/3aa13f1843d7c8fa16109b6395a9a29d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/3e2c06bdd9dcab29aeaeb0cfb7fa608a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/424123f9371ba357087363263eeafcda.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/43b3bbf43055c3e65db9cdf2c5b18c1c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/44704471b60c18b3ecec5300a88f1102.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/46604090efbb99c2db1800928af0a239.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/46998df85d31e629c0142f0556f5e7c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/49902f1c41b281f8eb3771abdd4dccb8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/4db4f31a16965baa42cb7dad667a8f04.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/572b9e28f1e6d89b765a16d809db84a1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/5921cef36750ff6092d94b83a802823b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/59e95d8b5332dcdae894a19946ab767d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/5ec349ff62b9ee5fe0c6dae867704dc4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/601f812e8b1eafecd5ba585a6a6d7962.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/6025ddaf99d86cc8bfd781006b19fbd6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/60f6e28b3f0400b0e8fe32c3c7116102.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/64a404d675f1d726f0891b7a80794595.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/7158022889c6c177062ac85036e7af10.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/7e38ca789a9e76ac91d9256374e154f0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/815a0b797465190f60d8b1a04656c42e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8726558cf297cbda831cc19514897205.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8855472f3c02f6c7ebb3216617ebe4af.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8e231d707f0c4f8e3cde90a6b52a79aa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9064c12fd72c7aba235d8c3881755b91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/911b9e53e9814de2998c60bf3115f560.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/92fdc376bce277874e75db666f175b44.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/930a23430f0eb64480d7fe5f82834e21.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/93bed0e5f2dd5a21cf73304fcfbc149d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/93f3ea6918533d96d9c252378b9a4af0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/95a0951d2a2722ff773a1a45e8c474d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/982cd1765ca6bbb3e6547e47834d5148.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9b0c3caac458c7bc9c9133e40405ddea.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9f3bdab4025dc7c5ba1a5871e62e8918.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a00e94d4f04df6aa659db9c4954c7efe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a2b0597837e382aca19919c4b74e58c8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a373e85c96aa0dc303092429fb837e51.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a6e044424780a57610833cc856c15bf5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a7c58070d68dc1724e9d4e781afb78db.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/accc43762ad05edfbff66ba1380d192a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ad47bcd6d4663026d648c132d969318d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/af57a2e8c72f9ba0efb1af771d32c124.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b0ae5f374e43dac992a4a5d334cebc0b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b91234c10fd8b8c8abc88e03afe66a1f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b9317198f118a1dfd8ddf2b82ec028f3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ba825ae79b1a6f7e0cce5215fcb5c96f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc6183ac08f0fac78c46f80c10cf7c92.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc8ccea5abf6598cf3cfa97eb59804bb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc95a792cbca6639214c9b0da13392ff.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bcfa62f35731856246c146d3a6932bf3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c063897793f593eb26d6ff7b7baaba18.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c6c2971ad1f3221f6cf84028aa0f477e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c97f41eef722121d86f55d553c056a39.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ca62704509932d3232d62918de97af3f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d1f064825fa5784b5c930652bd831cce.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d2faabcedd19f016e7b21bce073c0ec8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d320171e57480510f87dbbc7d5264b0a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d3a72a99d365dddfbca8d017a8011368.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d42aafa0f246ad3b4c16fe96d3a3a432.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d4aaf23c13ae808a4bed617afd13aa07.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d6bb686bddfbe8f38a36a68e609a8667.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d9020ff69a83b2a6ee1f42ae480f7db0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/da90a9012ab2d98f759e3fa0820ef502.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/df10d94bea357a43313e20da5d84bd30.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e34b2b141e472dc776c86fdf8eea23b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e444fd88f1390636e603d0d681538ac8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e79898628283edc27180fc39d0d769c1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/eaf332445a40942928e5f5750c1c3116.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/f91cd722855f4269256eae1187df64ec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fc895f5ce66b656f4a933097bf2a8775.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fca6720fd14c467d29a90f18ef3859b9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fd1bb507bcbf04856eeb7f7fd47ea579.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/noto-serif-jp.css +3349 -0
- package/src/styles/fonts/noto-serif-kr/400/033466ef683afe931f7f520cfb42d928.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/05da12edb9d52210581dc6ec4541031f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/0968e4861204b51f62a2f8e9f15dd5e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/0d5dec931dc885f07fe5cd5af8bed675.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/1076f0f6f66d28d7a2f16427faad4413.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/12ef1ba76bd20b004b865266a1aa70b3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/152395634a207579552f8cb54db88599.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/1a08931435f885e707edb85978ea3bb6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/31a197213ae61d7043c81013f52d10d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/37dbd564820cce2f7b3331e442da0df3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/37dc8505b20b37a2477a9121b16d44a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/3ac0a07878cacdc98bb889f40f5970d3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/491bbba374093fff045f55803a22bfef.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/4d38e56738156625329d93bd5c8cc74a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/57e9a86651366c1ba299e47aa7e358c2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/57f89adeae55aa7fe2add3fc1801ce03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/5df9ce98c75f8c50fb4fff7f01a23925.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/60020a830d809c26970cf8e48ba1eeda.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/60bcfa3ea7446eb42394aab02d28be40.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/63fafcb069520613d0ea35ad3e6b1e42.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/65a60d87c64228d258a123cbe85f5f31.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/6968e889e18891d912809fe484c2e745.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/70714891ad3fbfc3d5f10a8669dacc5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/761d84afca8d7f34eebefe538adba827.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/76e07530323418ec723c5d7634c9bcca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/773819b7b9b8fd404a929867c0fd677e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7817dd16805145d8538ad57590f69f5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7b59f0ec7792b18458dc5a361e37884c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7c3945788a689a69356c1a622d69d48b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/835b6505bb9eea9678925a1fa885353d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8b5c9ef81159f31d2ab35f45ca4373e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8b91c7c2ed390f1278b9befa3aa87233.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8fb45117a62d92dce44a80f0b729ead5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/90b7848e9b1623b77bdcf155e90b839c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/985d4d41afa0934a4eb2de35473fb9a2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9bb38fd05201de666b88b82d56a386f7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9eff2ab2a9d2ced47ab761bf6fdb11ec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9f1963fe8d4d6878d717d872a8f3fdb5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a0d00fe4816c95a8c7dffd445ef00b03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a0f199077fa1a33bc2a1e01c64de7fe6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a7c35e42a659347e490c3cb7983b2e7f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/aa662092cf249707123ca4d575e2764b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/aae1eda193285ab817a2d1b408440326.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b0cb664cb2e1371efda8943c0b7dcd1c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b0d1cdced482352cf0d3ae58638aacb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b320f5d185b2cff933ac549c184031c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ba9c59d7dfa4494db1bb764ada81467d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bab547459d514f46206e340c4bb2dc88.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bcc39eda837bb7a7a3d37c8c60fffb81.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bd73264d7f98776708d5d6f3c9b78fcc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bed610b217d500f5975cfc9fe6157570.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bf8901f8f11d4f433ea17dabc9370ea6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c4143bb9f2fe77b6ccf20088a8904650.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c7ff3f6bbdcd5f604b7343602ab904df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c945c62368357d05a53206620460fb30.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ce022e18a1377ac509443c3c3790b431.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/cf9cffe56636322f62b40d61130fbc5e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d20d8944bc0b85f5b2aae4b24f343516.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d36644d6502527e1fff205d0c7eca434.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d40ab99c7a38026f411c8f112f742b48.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/dc6e234ded795e91f76d6647f628fbf0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/dd0cfdfdac0866e66d587e2b5a9e9961.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/de304c8a02e45ded4f8dcc479d167198.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/e8c364c16daa04835bf32d293d2598db.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ec86e23683052da5cfdc3b77641bd15a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/eda1b0cb6d1719dd9bedcf3216a9e8de.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ee6fce20b420a480714607c66d7f97e5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/eee3836d6ac17ebb2c450bbcbc9db121.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/f5738255e92d8dd34a46d1bcdf4c4074.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/fa6e58ce4b52695e7ae19bbea6336ec8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/fd2069e6e8588c70b4f11364093b81f2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/04b8bde59cff68eeee74fb1914eb09c7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/05ac821472e235943ed1435e4bb8ecad.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/074c1f8483d5a4d8c45c8c5f3e4cbb32.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/1380210a722ac9aeef54005ad7b015c9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/17c7f5d0a45e92ede0e5dec3a2c77efa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/1be602ad456b0d75902d352116cb35fd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/2612c60f9dc5459ac42763591240a1d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/2ab8cf1f23a5ac7939a7876054e711a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/34e6750e00c3a911ef87dc66b336594d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/36f3df4730cfca4fc77fe52b52e6a126.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/37887deb7a9c466cf2af6ee7ff372ea6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3aba89c4d4281553078de4622b498cbb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3d88558097559d775231194d43745ba7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3dc3e8c35524cad2d1236f3393104ccf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/45140742dfa8686b58db2899ce89f89a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/469d2754e315e77270c12fa0ab28026c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4c0760284569b87d8f2290c324953973.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4cba92c942694bdc479bc54101bb1aa4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4e7d263c30bb48604069a3b4404d4eeb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/5279c7e465d9871edf51fd02e691eee7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/54bfdb6f21f9d6191e5100580239e14f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/56be5e32fd01668fe5ba814d94905839.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/615b38e0b5bb3ea68426f44f47706e6f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/65e9c4585d71bf48a5c62f367010da16.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/6b75213eb0be40ce84241eb2bb438a2e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7520ca6ca8c60eb1e62d50e71c8d30f1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/759a647b77791b1e98f99bc0ab5317a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/76e51630143b95b6322ef93ad330da7a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/79aa7c8c842c4a27cf57c0a3a1ffca5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7cc72fd2c9105560422b6a67c6162945.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7d0ea0690e432462f4d05a23d720ec19.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7dffa5c1bec57e0903fd62357401ff1a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/82f6ccc063960eed1cdfd1d61ada8862.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/845b4b67564d62cf5cad242f7ac08ea5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/84c8e7bc0931008ed91f44ed12dfea94.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/887a11290ba78b1e66c6d2f67043005e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/88db1d042074fb6e66821ffc10941930.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/8dc18c0cebe6aa4bf4c45dbb831048ab.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/919a879bd2d580d8491a31a449390689.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/91da6cb174bebfb96976e2f1316be2fe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/934dfdbed2bb2c4b6129199d699a34fa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/97d7a17234f2b5030ae9697ae00aded2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/995c4fda62d25c3b79dd5987737df6ae.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/9d0afa53dc2f92ba42024f55f11f6779.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/9e13a47242926f1cd7d68c08d9ef8889.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a379ada89abd750c587ded29f77e731c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a48be56ce5d3a99dc8f8331398004412.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a806759e72987951d6d08b7cbdf36a1b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a88481ec8bc7be11cb66e46781a79bb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a9a016d4a93409f65278327e8a8bb38d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/ab92d41062a7644faa45f50bf384454a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b326a8a9c33b554db570da94f60bc380.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b6117ff2993b11bb1fdc7ea3588a010c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b61982951bd51b724143c30dfaaa9fe9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c0098958e20db68cab90097b5e62516f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c4bfaa5e50798246e3770718b7a7c84a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c84598999133455503042e06f4ab79cb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/ca9a533988d7019597a60d4e17127e0c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/cf2b28f90f47276f7e2688a65e88a101.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/d689b1861d7e4377dd72ad3013482612.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/d9047070d72a816b3dba9d40c2d85e69.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/da04549f3f4ed28076b01b8cd710d313.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/da7af303f8c645f9a9dbae0e6e32dd35.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/df9568257eb29b156449fdd4bec5ec76.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e067cd0ed76c90cd0a93c9339253f20b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e08b07772e7bed3cec2832d43f7fd339.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e12150d5a39b30be8f567968c7a527b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e53fcb2381eee345db4f6f973dd95a3e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e5bd313ef81f687d398aacb11cec3069.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/eb5afb3d952b8593782caec6026514b6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/edd6a4f608d04fc0351d7688cfc321e4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/f9695c6c4df2bf6bc03045ff79d4f01f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/fada6eaa68ff8816afe43d2a36c5423e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/fbcc4bf5367218951172bdee6f77d7a6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/noto-serif-kr.css +2601 -0
- package/src/styles/site-media.css +823 -0
- package/src/styles/tokens.css +292 -21
- package/src/styles/ui.css +4188 -1857
- package/src/types/app-context.ts +20 -0
- package/src/types/bindings.ts +3 -1
- package/src/types/config.ts +147 -9
- package/src/types/constants.ts +46 -11
- package/src/types/entities.ts +24 -8
- package/src/types/operations.ts +80 -1
- package/src/types/props.ts +10 -9
- package/src/types/raw-assets.d.ts +39 -0
- package/src/types/views.ts +28 -7
- package/src/ui/__tests__/color-themes.test.ts +7 -40
- package/src/ui/__tests__/font-themes.test.ts +30 -11
- package/src/ui/color-themes.ts +311 -270
- package/src/ui/compose/ComposeDialog.tsx +712 -394
- package/src/ui/compose/ComposePrompt.tsx +15 -10
- package/src/ui/dash/ActionButtons.tsx +27 -18
- package/src/ui/dash/DangerZone.tsx +15 -10
- package/src/ui/dash/FormatBadge.tsx +21 -8
- package/src/ui/dash/StatusBadge.tsx +33 -22
- package/src/ui/dash/appearance/AdvancedContent.tsx +44 -30
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +164 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +127 -160
- package/src/ui/dash/appearance/FontThemeContent.tsx +20 -15
- package/src/ui/dash/appearance/NavigationContent.tsx +368 -225
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +54 -0
- package/src/ui/dash/settings/AccountContent.tsx +34 -23
- package/src/ui/dash/settings/AccountMenuContent.tsx +226 -137
- package/src/ui/dash/settings/ApiTokensContent.tsx +122 -79
- package/src/ui/dash/settings/AvatarContent.tsx +73 -45
- package/src/ui/dash/settings/DeleteAccountContent.tsx +106 -66
- package/src/ui/dash/settings/GeneralContent.tsx +274 -157
- package/src/ui/dash/settings/GitHubSyncContent.tsx +558 -0
- package/src/ui/dash/settings/SessionsContent.tsx +79 -38
- package/src/ui/dash/settings/SettingsRootContent.tsx +209 -109
- package/src/ui/dash/settings/__tests__/AccountMenuContent.test.tsx +67 -0
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +75 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +24 -14
- package/src/ui/feed/LinkCard.tsx +116 -84
- package/src/ui/feed/LinkPreview.tsx +75 -0
- package/src/ui/feed/NoteCard.tsx +60 -16
- package/src/ui/feed/PostStatusBadges.tsx +15 -0
- package/src/ui/feed/QuoteCard.tsx +10 -3
- package/src/ui/feed/ThreadPreview.tsx +62 -36
- package/src/ui/feed/TimelineFeed.tsx +2 -1
- package/src/ui/feed/__tests__/thread-preview.test.ts +220 -11
- package/src/ui/feed/__tests__/timeline-cards.test.ts +186 -5
- package/src/ui/feed/thread-preview-state.ts +23 -10
- package/src/ui/font-themes.ts +57 -97
- package/src/ui/layouts/BaseLayout.tsx +230 -136
- package/src/ui/layouts/SiteLayout.tsx +431 -143
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +76 -6
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +76 -0
- package/src/ui/pages/ArchivePage.tsx +320 -207
- package/src/ui/pages/BrandPage.tsx +499 -341
- package/src/ui/pages/CollectionEditorPage.tsx +50 -30
- package/src/ui/pages/CollectionPage.tsx +151 -78
- package/src/ui/pages/CollectionsPage.tsx +27 -18
- package/src/ui/pages/ComposePage.tsx +15 -10
- package/src/ui/pages/FeaturedPage.tsx +16 -11
- package/src/ui/pages/HomePage.tsx +15 -10
- package/src/ui/pages/PostPage.tsx +5 -1
- package/src/ui/pages/SearchPage.tsx +93 -43
- package/src/ui/pages/ThemeSamplePage.tsx +758 -520
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +72 -0
- package/src/ui/shared/CollectionDirectory.tsx +215 -34
- package/src/ui/shared/CollectionsManager.tsx +155 -75
- package/src/ui/shared/DecorativeQuoteMark.tsx +12 -9
- package/src/ui/shared/MediaGallery.tsx +350 -256
- package/src/ui/shared/Pagination.tsx +36 -25
- package/src/ui/shared/PostFooter.tsx +139 -96
- package/src/ui/shared/__tests__/media-gallery.test.ts +27 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +79 -28
- package/src/ui/shared/__tests__/post-footer.test.ts +42 -17
- package/src/ui/shared/collection-management-labels.ts +109 -33
- package/src/ui/shared/navigation-labels.ts +78 -8
- package/bin/lib/site-localize-media.js +0 -427
- package/dist/client/_assets/client-auth.js +0 -3260
- package/dist/client/_assets/client.css +0 -2
- package/dist/client/_assets/client.js +0 -380
- package/src/__tests__/site-localize-media.test.ts +0 -150
- package/src/client/components/jant-collection-sidebar.ts +0 -816
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/zh-Hans.po +0 -3197
- package/src/i18n/locales/zh-Hans.ts +0 -1
- package/src/i18n/locales/zh-Hant.po +0 -3197
- package/src/i18n/locales/zh-Hant.ts +0 -1
- package/src/routes/feed/rss.ts +0 -216
- package/src/types/lingui-react-macro.d.ts +0 -34
- /package/dist/client/_assets/chunks/{heic-to-XcUDQvtx.js → heic-to-DUUaO23q.js} +0 -0
- /package/dist/client/_assets/chunks/{module-ChVQstFd.js → module-DcsAZQZ_.js} +0 -0
- /package/dist/client/_assets/chunks/{native-CR5HLOyf.js → native-DpcrFAPh.js} +0 -0
- /package/dist/client/_assets/{client-cjk.css → client-cjk-B7Z0snDu.css} +0 -0
- /package/dist/client/_assets/{client-cjk-tc.css → client-cjk-tc-BesJYrb2.css} +0 -0
package/src/services/post.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import {
|
|
10
10
|
eq,
|
|
11
11
|
and,
|
|
12
|
+
or,
|
|
12
13
|
type SQL,
|
|
13
14
|
type SQLWrapper,
|
|
14
15
|
isNull,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
isNotNull,
|
|
19
20
|
asc,
|
|
20
21
|
lte,
|
|
22
|
+
gt,
|
|
21
23
|
} from "drizzle-orm";
|
|
22
24
|
import {
|
|
23
25
|
type Database,
|
|
@@ -31,9 +33,10 @@ import {
|
|
|
31
33
|
} from "../db/schema-bundle.js";
|
|
32
34
|
import { createEntityId } from "../lib/ids.js";
|
|
33
35
|
import { now } from "../lib/time.js";
|
|
34
|
-
import { renderTiptapJson } from "../lib/tiptap-render.js";
|
|
36
|
+
import { renderTiptapJson, trimTiptapBody } from "../lib/tiptap-render.js";
|
|
35
37
|
import { extractSummary, extractBodyText } from "../lib/summary.js";
|
|
36
38
|
import { markdownToTiptapJson } from "../lib/markdown-to-tiptap.js";
|
|
39
|
+
import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
|
|
37
40
|
import { generatePostSlug } from "../lib/slug.js";
|
|
38
41
|
import { getSlugValidationIssue } from "../lib/slug-format.js";
|
|
39
42
|
import { normalizePath, slugify } from "../lib/url.js";
|
|
@@ -64,6 +67,12 @@ import {
|
|
|
64
67
|
NotFoundError,
|
|
65
68
|
} from "../lib/errors.js";
|
|
66
69
|
import { createPathService, type PathService } from "./path.js";
|
|
70
|
+
import {
|
|
71
|
+
extractYouTubeVideoId,
|
|
72
|
+
getYouTubeThumbnailUrls,
|
|
73
|
+
} from "../lib/youtube.js";
|
|
74
|
+
import { getPreviewStorageKey } from "../lib/upload.js";
|
|
75
|
+
import { generateRandomId } from "../lib/nanoid.js";
|
|
67
76
|
|
|
68
77
|
/** Dependencies for operations that coordinate with other services */
|
|
69
78
|
export interface PostDeleteDeps {
|
|
@@ -139,8 +148,29 @@ export interface CollectionFeedEntry {
|
|
|
139
148
|
collectedAt: number;
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
export interface PostBodyContent {
|
|
152
|
+
id: string;
|
|
153
|
+
type: "post";
|
|
154
|
+
format: Format;
|
|
155
|
+
contentFormat: "markdown";
|
|
156
|
+
content: string;
|
|
157
|
+
chars: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Minimal projection used by the sitemap renderer. */
|
|
161
|
+
export interface SitemapPostEntry {
|
|
162
|
+
id: string;
|
|
163
|
+
/** Canonical slug from `path_registry` */
|
|
164
|
+
slug: string;
|
|
165
|
+
/** Primary alias, if the post has one; used in preference to `slug` for URLs */
|
|
166
|
+
alias: string | null;
|
|
167
|
+
updatedAt: number;
|
|
168
|
+
featuredAt: number | null;
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
export interface PostService {
|
|
143
172
|
getById(id: string): Promise<Post | null>;
|
|
173
|
+
getBodyContent(id: string): Promise<PostBodyContent | null>;
|
|
144
174
|
getBySlug(slug: string): Promise<Post | null>;
|
|
145
175
|
suggestSlug(input: {
|
|
146
176
|
title?: string;
|
|
@@ -149,6 +179,35 @@ export interface PostService {
|
|
|
149
179
|
}): Promise<string>;
|
|
150
180
|
checkSlugAvailability(slug: string, excludePostId?: string): Promise<boolean>;
|
|
151
181
|
list(filters?: PostFilters): Promise<Post[]>;
|
|
182
|
+
/**
|
|
183
|
+
* List minimal fields needed to render sitemap entries, paginated by `id`
|
|
184
|
+
* (ascending). Excludes replies, private posts, deleted posts, and drafts.
|
|
185
|
+
*
|
|
186
|
+
* Uses keyset pagination on the primary key so old sitemap shards are cheap
|
|
187
|
+
* to serve and stable across shard boundaries: a newly created post always
|
|
188
|
+
* gets a larger TypeID than any previously-committed post, so it lands in
|
|
189
|
+
* the last shard and never rewrites older ones.
|
|
190
|
+
*
|
|
191
|
+
* @param options.afterId Exclusive lower bound on `id`. Omit for the first
|
|
192
|
+
* shard.
|
|
193
|
+
* @param options.limit Maximum rows to return.
|
|
194
|
+
*/
|
|
195
|
+
listForSitemap(options: {
|
|
196
|
+
afterId?: string;
|
|
197
|
+
limit: number;
|
|
198
|
+
}): Promise<SitemapPostEntry[]>;
|
|
199
|
+
/** Count posts that qualify for the sitemap (same filters as `listForSitemap`) */
|
|
200
|
+
countForSitemap(): Promise<number>;
|
|
201
|
+
/**
|
|
202
|
+
* Return the id at the given 0-based offset in the sitemap ordering.
|
|
203
|
+
* Used to compute keyset cursors for sharded sitemap endpoints.
|
|
204
|
+
*
|
|
205
|
+
* Returns `null` when the offset is beyond the available rows.
|
|
206
|
+
*
|
|
207
|
+
* Walks the primary-key index with `ORDER BY id ASC LIMIT 1 OFFSET ?` —
|
|
208
|
+
* SQLite/D1 scan only the index for this, not the row data.
|
|
209
|
+
*/
|
|
210
|
+
getSitemapIdAt(offset: number): Promise<string | null>;
|
|
152
211
|
/** Count posts matching filters (ignores cursor, offset, limit) */
|
|
153
212
|
count(filters?: PostFilters): Promise<number>;
|
|
154
213
|
/** Count posts matching filters up to a fixed limit (ignores cursor, offset, limit) */
|
|
@@ -164,6 +223,24 @@ export interface PostService {
|
|
|
164
223
|
deps: PostAttachmentDeps,
|
|
165
224
|
summaryConfig?: SummaryConfig,
|
|
166
225
|
): Promise<Post>;
|
|
226
|
+
/**
|
|
227
|
+
* Atomically create a thread of posts. The first item is the root; each
|
|
228
|
+
* subsequent item is automatically chained as a reply to the previous one.
|
|
229
|
+
* On failure, all already-created posts are rolled back.
|
|
230
|
+
*
|
|
231
|
+
* @param items - Ordered list of (data, attachments) pairs; at least 2 required
|
|
232
|
+
* @param deps - Media/storage dependencies
|
|
233
|
+
* @param summaryConfig - Optional summary extraction config
|
|
234
|
+
* @returns Ordered list of created posts; posts[0] is the root
|
|
235
|
+
*/
|
|
236
|
+
createThreadWithAttachments(
|
|
237
|
+
items: Array<{
|
|
238
|
+
data: CreatePost;
|
|
239
|
+
attachments: PostAttachmentInput[] | undefined;
|
|
240
|
+
}>,
|
|
241
|
+
deps: PostAttachmentDeps,
|
|
242
|
+
summaryConfig?: SummaryConfig,
|
|
243
|
+
): Promise<Post[]>;
|
|
167
244
|
update(
|
|
168
245
|
id: string,
|
|
169
246
|
data: UpdatePost,
|
|
@@ -184,6 +261,11 @@ export interface PostService {
|
|
|
184
261
|
* @param deps - Media service and optional storage driver for file cleanup
|
|
185
262
|
*/
|
|
186
263
|
delete(id: string, deps?: PostDeleteDeps): Promise<boolean>;
|
|
264
|
+
/**
|
|
265
|
+
* Delete a thread draft and release its slug paths so they can be reused.
|
|
266
|
+
* Used when replacing a saved thread draft with a new version.
|
|
267
|
+
*/
|
|
268
|
+
deleteThreadDraft(id: string, deps?: PostDeleteDeps): Promise<boolean>;
|
|
187
269
|
getThread(rootId: string): Promise<Post[]>;
|
|
188
270
|
updateThreadStatusAndVisibility(
|
|
189
271
|
rootId: string,
|
|
@@ -251,6 +333,28 @@ export interface PostService {
|
|
|
251
333
|
getDistinctYears(filters?: PostFilters): Promise<number[]>;
|
|
252
334
|
/** For each thread ID, return the ID of the last published, non-deleted post */
|
|
253
335
|
getLastPostIdsByThread(threadIds: string[]): Promise<Map<string, string>>;
|
|
336
|
+
/**
|
|
337
|
+
* Rebuild `post.body_text` for a batch of non-deleted posts, cursor-paginated
|
|
338
|
+
* by post id. For each row, recomputes the plain-text extraction via
|
|
339
|
+
* `extractBodyText(body)` and writes it back only when it differs from the
|
|
340
|
+
* stored value. FTS indexes (SQLite trigger / Postgres generated column)
|
|
341
|
+
* refresh automatically on the UPDATE.
|
|
342
|
+
*
|
|
343
|
+
* Idempotent: re-running after a no-op pass returns `updated: 0`.
|
|
344
|
+
*
|
|
345
|
+
* @param options.limit Batch size (1..500, default 50)
|
|
346
|
+
* @param options.cursor Exclusive lower bound on post id; pass the previous
|
|
347
|
+
* response's `nextCursor` to continue
|
|
348
|
+
* @returns processed/updated/skipped counts, the next cursor, and a `done`
|
|
349
|
+
* flag the caller uses to terminate the loop
|
|
350
|
+
*/
|
|
351
|
+
reindexBodyText(options?: { limit?: number; cursor?: string }): Promise<{
|
|
352
|
+
processed: number;
|
|
353
|
+
updated: number;
|
|
354
|
+
skipped: number;
|
|
355
|
+
nextCursor: string | null;
|
|
356
|
+
done: boolean;
|
|
357
|
+
}>;
|
|
254
358
|
}
|
|
255
359
|
|
|
256
360
|
const SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
|
|
@@ -259,24 +363,8 @@ function isValidSlug(value: string): boolean {
|
|
|
259
363
|
return SLUG_RE.test(value);
|
|
260
364
|
}
|
|
261
365
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
let current: unknown = err;
|
|
265
|
-
while (current) {
|
|
266
|
-
const msg = String(current);
|
|
267
|
-
if (
|
|
268
|
-
msg.includes("UNIQUE constraint") ||
|
|
269
|
-
msg.includes("SQLITE_CONSTRAINT")
|
|
270
|
-
) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
current =
|
|
274
|
-
current instanceof Error && current.cause !== current
|
|
275
|
-
? current.cause
|
|
276
|
-
: undefined;
|
|
277
|
-
}
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
366
|
+
// Re-export shared constraint detection — see db/dialect.ts
|
|
367
|
+
import { isUniqueConstraintError } from "../db/dialect.js";
|
|
280
368
|
|
|
281
369
|
function hasNonEmptyText(value: string | null | undefined): boolean {
|
|
282
370
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -349,8 +437,10 @@ function ensurePostRating(
|
|
|
349
437
|
function assertPostFormatShape(data: {
|
|
350
438
|
format: Format;
|
|
351
439
|
title?: string | null;
|
|
440
|
+
body?: unknown;
|
|
352
441
|
url?: string | null;
|
|
353
442
|
quoteText?: string | null;
|
|
443
|
+
hasAttachments?: boolean;
|
|
354
444
|
}): void {
|
|
355
445
|
const hasTitle = hasNonEmptyText(data.title);
|
|
356
446
|
const hasUrl = hasNonEmptyText(data.url);
|
|
@@ -363,6 +453,18 @@ function assertPostFormatShape(data: {
|
|
|
363
453
|
if (hasQuoteText) {
|
|
364
454
|
throw new ValidationError("Notes can't include quoted text.");
|
|
365
455
|
}
|
|
456
|
+
// hasAttachments === undefined means unknown (e.g. update without
|
|
457
|
+
// attachment changes) — skip the empty-content check in that case.
|
|
458
|
+
if (
|
|
459
|
+
!hasTitle &&
|
|
460
|
+
!data.body &&
|
|
461
|
+
data.hasAttachments !== undefined &&
|
|
462
|
+
!data.hasAttachments
|
|
463
|
+
) {
|
|
464
|
+
throw new ValidationError(
|
|
465
|
+
"Notes need a title, body, or at least one attachment.",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
366
468
|
return;
|
|
367
469
|
}
|
|
368
470
|
|
|
@@ -497,6 +599,87 @@ export function createPostService(
|
|
|
497
599
|
return [...new Set(collectionIds)];
|
|
498
600
|
}
|
|
499
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Fetch a link preview thumbnail and store it.
|
|
604
|
+
* Silently returns without updating if the URL is not a recognized provider
|
|
605
|
+
* or if the fetch fails.
|
|
606
|
+
*/
|
|
607
|
+
async function resolveAndStorePreview(
|
|
608
|
+
postId: string,
|
|
609
|
+
format: string,
|
|
610
|
+
url: string | null | undefined,
|
|
611
|
+
storage: StorageDriver | null | undefined,
|
|
612
|
+
): Promise<void> {
|
|
613
|
+
if (!storage || format !== "link" || !url) return;
|
|
614
|
+
|
|
615
|
+
const videoId = extractYouTubeVideoId(url);
|
|
616
|
+
if (!videoId) return;
|
|
617
|
+
|
|
618
|
+
const thumbnailUrls = getYouTubeThumbnailUrls(videoId);
|
|
619
|
+
let imageBytes: Uint8Array | null = null;
|
|
620
|
+
let contentType = "image/jpeg";
|
|
621
|
+
|
|
622
|
+
for (const thumbUrl of thumbnailUrls) {
|
|
623
|
+
try {
|
|
624
|
+
const response = await fetch(thumbUrl);
|
|
625
|
+
if (response.ok) {
|
|
626
|
+
const ct = response.headers.get("content-type");
|
|
627
|
+
// YouTube returns a grey placeholder (120×90) when maxresdefault
|
|
628
|
+
// is not available. Detect it by checking content-length.
|
|
629
|
+
const cl = response.headers.get("content-length");
|
|
630
|
+
if (cl && parseInt(cl, 10) < 5000) {
|
|
631
|
+
// Too small — likely a placeholder, try next quality
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
imageBytes = new Uint8Array(await response.arrayBuffer());
|
|
635
|
+
if (ct) contentType = ct;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
// Network failure — try next URL
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (!imageBytes) return;
|
|
644
|
+
|
|
645
|
+
const ext = contentType.includes("webp")
|
|
646
|
+
? "webp"
|
|
647
|
+
: contentType.includes("png")
|
|
648
|
+
? "png"
|
|
649
|
+
: "jpg";
|
|
650
|
+
const suffix = generateRandomId(8);
|
|
651
|
+
const storageKey = getPreviewStorageKey(siteId, postId, suffix, ext);
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
await storage.put(storageKey, imageBytes, { contentType });
|
|
655
|
+
await db
|
|
656
|
+
.update(posts)
|
|
657
|
+
.set({
|
|
658
|
+
previewImageKey: storageKey,
|
|
659
|
+
previewKind: "video",
|
|
660
|
+
previewProvider: "youtube",
|
|
661
|
+
})
|
|
662
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, postId)));
|
|
663
|
+
} catch {
|
|
664
|
+
// Storage or DB failure — preview is best-effort
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Delete a preview image from storage if it exists.
|
|
670
|
+
*/
|
|
671
|
+
async function deletePreviewImage(
|
|
672
|
+
previewImageKey: string | null | undefined,
|
|
673
|
+
storage: StorageDriver | null | undefined,
|
|
674
|
+
): Promise<void> {
|
|
675
|
+
if (!storage || !previewImageKey) return;
|
|
676
|
+
try {
|
|
677
|
+
await storage.delete(previewImageKey);
|
|
678
|
+
} catch {
|
|
679
|
+
// Best-effort cleanup
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
500
683
|
function buildCollectionMembershipCondition(
|
|
501
684
|
collectionIds: readonly string[],
|
|
502
685
|
): SQL<unknown> {
|
|
@@ -631,6 +814,25 @@ export function createPostService(
|
|
|
631
814
|
return conditions;
|
|
632
815
|
}
|
|
633
816
|
|
|
817
|
+
async function getLastLivePostIdInThread(
|
|
818
|
+
threadId: string,
|
|
819
|
+
): Promise<string | null> {
|
|
820
|
+
const rows = await db
|
|
821
|
+
.select({ id: posts.id })
|
|
822
|
+
.from(posts)
|
|
823
|
+
.where(
|
|
824
|
+
and(
|
|
825
|
+
eq(posts.siteId, siteId),
|
|
826
|
+
eq(posts.threadId, threadId),
|
|
827
|
+
isNull(posts.deletedAt),
|
|
828
|
+
),
|
|
829
|
+
)
|
|
830
|
+
.orderBy(desc(posts.createdAt), desc(posts.id))
|
|
831
|
+
.limit(1);
|
|
832
|
+
|
|
833
|
+
return rows[0]?.id ?? null;
|
|
834
|
+
}
|
|
835
|
+
|
|
634
836
|
function getCursorSortTimestamp(row: typeof posts.$inferSelect): number {
|
|
635
837
|
return row.status === "draft" ? row.updatedAt : (row.lastActivityAt ?? -1);
|
|
636
838
|
}
|
|
@@ -790,6 +992,9 @@ export function createPostService(
|
|
|
790
992
|
quoteText: row.quoteText,
|
|
791
993
|
summary: row.summary,
|
|
792
994
|
rating: ensurePostRating(row.rating, Error),
|
|
995
|
+
previewImageKey: row.previewImageKey,
|
|
996
|
+
previewKind: row.previewKind,
|
|
997
|
+
previewProvider: row.previewProvider,
|
|
793
998
|
replyToId: row.replyToId,
|
|
794
999
|
threadId: row.threadId,
|
|
795
1000
|
deletedAt: row.deletedAt,
|
|
@@ -981,6 +1186,24 @@ export function createPostService(
|
|
|
981
1186
|
return rows.map((row) => row.collectionId);
|
|
982
1187
|
}
|
|
983
1188
|
|
|
1189
|
+
async function getPostCollectionTimestamps(
|
|
1190
|
+
postId: string,
|
|
1191
|
+
): Promise<Map<string, number>> {
|
|
1192
|
+
const rows = await db
|
|
1193
|
+
.select({
|
|
1194
|
+
collectionId: postCollections.collectionId,
|
|
1195
|
+
createdAt: postCollections.createdAt,
|
|
1196
|
+
})
|
|
1197
|
+
.from(postCollections)
|
|
1198
|
+
.where(
|
|
1199
|
+
and(
|
|
1200
|
+
eq(postCollections.siteId, siteId),
|
|
1201
|
+
eq(postCollections.postId, postId),
|
|
1202
|
+
),
|
|
1203
|
+
);
|
|
1204
|
+
return new Map(rows.map((r) => [r.collectionId, r.createdAt]));
|
|
1205
|
+
}
|
|
1206
|
+
|
|
984
1207
|
function buildRollbackUpdate(
|
|
985
1208
|
post: Post,
|
|
986
1209
|
collectionIds: string[],
|
|
@@ -1018,6 +1241,20 @@ export function createPostService(
|
|
|
1018
1241
|
return hydratePost(result[0]);
|
|
1019
1242
|
},
|
|
1020
1243
|
|
|
1244
|
+
async getBodyContent(id) {
|
|
1245
|
+
const post = await this.getById(id);
|
|
1246
|
+
if (!post) return null;
|
|
1247
|
+
|
|
1248
|
+
return {
|
|
1249
|
+
id: post.id,
|
|
1250
|
+
type: "post",
|
|
1251
|
+
format: post.format,
|
|
1252
|
+
contentFormat: "markdown",
|
|
1253
|
+
content: post.body ? tiptapJsonToMarkdown(post.body) : "",
|
|
1254
|
+
chars: post.bodyText?.length ?? 0,
|
|
1255
|
+
};
|
|
1256
|
+
},
|
|
1257
|
+
|
|
1021
1258
|
async getBySlug(slug) {
|
|
1022
1259
|
const resolved = await resolvedPaths.resolve(slug);
|
|
1023
1260
|
if (!resolved || resolved.kind !== "slug" || !resolved.postId) {
|
|
@@ -1073,6 +1310,14 @@ export function createPostService(
|
|
|
1073
1310
|
ELSE 1
|
|
1074
1311
|
END`;
|
|
1075
1312
|
|
|
1313
|
+
// NULL-sort order differs between dialects: SQLite puts NULLs last for
|
|
1314
|
+
// DESC, Postgres puts them first. Wrap nullable sort keys in COALESCE
|
|
1315
|
+
// so pinned/featured posts land on top under both engines. Mirrors the
|
|
1316
|
+
// expressions used by `buildListCursorCondition` above.
|
|
1317
|
+
const pinnedSortExpr = sql<number>`coalesce(${posts.pinnedAt}, -1)`;
|
|
1318
|
+
const featuredSortExpr = sql<number>`coalesce(${posts.featuredAt}, -1)`;
|
|
1319
|
+
const sortTimestampSortExpr = sql<number>`coalesce(${sortTimestamp}, -1)`;
|
|
1320
|
+
|
|
1076
1321
|
const baseQuery = db
|
|
1077
1322
|
.select()
|
|
1078
1323
|
.from(posts)
|
|
@@ -1082,29 +1327,31 @@ export function createPostService(
|
|
|
1082
1327
|
let query =
|
|
1083
1328
|
filters.featured || filters.sortOrder === undefined
|
|
1084
1329
|
? baseQuery.orderBy(
|
|
1085
|
-
desc(
|
|
1086
|
-
filters.featured
|
|
1330
|
+
desc(pinnedSortExpr),
|
|
1331
|
+
filters.featured
|
|
1332
|
+
? desc(featuredSortExpr)
|
|
1333
|
+
: desc(sortTimestampSortExpr),
|
|
1087
1334
|
desc(posts.id),
|
|
1088
1335
|
)
|
|
1089
1336
|
: filters.sortOrder === "oldest"
|
|
1090
1337
|
? baseQuery.orderBy(
|
|
1091
|
-
desc(
|
|
1092
|
-
asc(
|
|
1338
|
+
desc(pinnedSortExpr),
|
|
1339
|
+
asc(sortTimestampSortExpr),
|
|
1093
1340
|
asc(posts.id),
|
|
1094
1341
|
)
|
|
1095
1342
|
: filters.sortOrder === "rating_desc"
|
|
1096
1343
|
? baseQuery.orderBy(
|
|
1097
|
-
desc(
|
|
1344
|
+
desc(pinnedSortExpr),
|
|
1098
1345
|
desc(ratingPresence),
|
|
1099
1346
|
desc(posts.rating),
|
|
1100
|
-
desc(
|
|
1347
|
+
desc(sortTimestampSortExpr),
|
|
1101
1348
|
desc(posts.id),
|
|
1102
1349
|
)
|
|
1103
1350
|
: baseQuery.orderBy(
|
|
1104
|
-
desc(
|
|
1351
|
+
desc(pinnedSortExpr),
|
|
1105
1352
|
desc(ratingPresence),
|
|
1106
1353
|
asc(posts.rating),
|
|
1107
|
-
desc(
|
|
1354
|
+
desc(sortTimestampSortExpr),
|
|
1108
1355
|
desc(posts.id),
|
|
1109
1356
|
);
|
|
1110
1357
|
|
|
@@ -1116,11 +1363,88 @@ export function createPostService(
|
|
|
1116
1363
|
return hydratePosts(rows);
|
|
1117
1364
|
},
|
|
1118
1365
|
|
|
1366
|
+
async listForSitemap({ afterId, limit }) {
|
|
1367
|
+
// Share the filter conditions with `list()` so visibility/reply/deleted
|
|
1368
|
+
// semantics stay consistent if they ever change.
|
|
1369
|
+
const conditions = buildFilterConditions({
|
|
1370
|
+
status: "published",
|
|
1371
|
+
excludePrivate: true,
|
|
1372
|
+
excludeReplies: true,
|
|
1373
|
+
});
|
|
1374
|
+
if (afterId !== undefined) {
|
|
1375
|
+
conditions.push(sql`${posts.id} > ${afterId}`);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const rows = await db
|
|
1379
|
+
.select({
|
|
1380
|
+
id: posts.id,
|
|
1381
|
+
updatedAt: posts.updatedAt,
|
|
1382
|
+
featuredAt: posts.featuredAt,
|
|
1383
|
+
})
|
|
1384
|
+
.from(posts)
|
|
1385
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1386
|
+
.orderBy(asc(posts.id))
|
|
1387
|
+
.limit(limit);
|
|
1388
|
+
|
|
1389
|
+
if (rows.length === 0) return [];
|
|
1390
|
+
|
|
1391
|
+
const ids = rows.map((row) => row.id);
|
|
1392
|
+
const [slugMap, aliasesMap] = await Promise.all([
|
|
1393
|
+
resolvedPaths.getPostSlugMap(ids),
|
|
1394
|
+
resolvedPaths.getPostAliases(ids),
|
|
1395
|
+
]);
|
|
1396
|
+
|
|
1397
|
+
return rows
|
|
1398
|
+
.map((row): SitemapPostEntry | null => {
|
|
1399
|
+
const slug = slugMap.get(row.id);
|
|
1400
|
+
if (!slug) return null;
|
|
1401
|
+
const alias = aliasesMap.get(row.id)?.[0] ?? null;
|
|
1402
|
+
return {
|
|
1403
|
+
id: row.id,
|
|
1404
|
+
slug,
|
|
1405
|
+
alias,
|
|
1406
|
+
updatedAt: row.updatedAt,
|
|
1407
|
+
featuredAt: row.featuredAt,
|
|
1408
|
+
};
|
|
1409
|
+
})
|
|
1410
|
+
.filter((entry): entry is SitemapPostEntry => entry !== null);
|
|
1411
|
+
},
|
|
1412
|
+
|
|
1413
|
+
async countForSitemap() {
|
|
1414
|
+
const conditions = buildFilterConditions({
|
|
1415
|
+
status: "published",
|
|
1416
|
+
excludePrivate: true,
|
|
1417
|
+
excludeReplies: true,
|
|
1418
|
+
});
|
|
1419
|
+
const result = await db
|
|
1420
|
+
.select({ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count") })
|
|
1421
|
+
.from(posts)
|
|
1422
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined);
|
|
1423
|
+
return result[0]?.count ?? 0;
|
|
1424
|
+
},
|
|
1425
|
+
|
|
1426
|
+
async getSitemapIdAt(offset) {
|
|
1427
|
+
if (offset < 0) return null;
|
|
1428
|
+
const conditions = buildFilterConditions({
|
|
1429
|
+
status: "published",
|
|
1430
|
+
excludePrivate: true,
|
|
1431
|
+
excludeReplies: true,
|
|
1432
|
+
});
|
|
1433
|
+
const rows = await db
|
|
1434
|
+
.select({ id: posts.id })
|
|
1435
|
+
.from(posts)
|
|
1436
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1437
|
+
.orderBy(asc(posts.id))
|
|
1438
|
+
.limit(1)
|
|
1439
|
+
.offset(offset);
|
|
1440
|
+
return rows[0]?.id ?? null;
|
|
1441
|
+
},
|
|
1442
|
+
|
|
1119
1443
|
async count(filters = {}) {
|
|
1120
1444
|
const conditions = buildFilterConditions(filters);
|
|
1121
1445
|
|
|
1122
1446
|
const result = await db
|
|
1123
|
-
.select({ count: sql<number>`count(*)`.as("count") })
|
|
1447
|
+
.select({ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count") })
|
|
1124
1448
|
.from(posts)
|
|
1125
1449
|
.where(conditions.length > 0 ? and(...conditions) : undefined);
|
|
1126
1450
|
|
|
@@ -1153,7 +1477,7 @@ export function createPostService(
|
|
|
1153
1477
|
return db
|
|
1154
1478
|
.select({
|
|
1155
1479
|
yearMonth: publishedYearMonthExpr.as("year_month"),
|
|
1156
|
-
count: sql<number>`count(*)`.as("count"),
|
|
1480
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
1157
1481
|
})
|
|
1158
1482
|
.from(posts)
|
|
1159
1483
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
@@ -1173,22 +1497,33 @@ export function createPostService(
|
|
|
1173
1497
|
: undefined;
|
|
1174
1498
|
const rating = ensurePostRating(data.rating);
|
|
1175
1499
|
|
|
1500
|
+
const rawBody = data.bodyMarkdown
|
|
1501
|
+
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1502
|
+
: (data.body ?? null);
|
|
1503
|
+
const body = rawBody ? trimTiptapBody(rawBody) : null;
|
|
1504
|
+
const title = data.title?.trim() || null;
|
|
1505
|
+
const quoteText = data.quoteText?.trim() || null;
|
|
1506
|
+
const url = data.url?.trim() || null;
|
|
1507
|
+
|
|
1176
1508
|
assertPostFormatShape({
|
|
1177
1509
|
format,
|
|
1178
|
-
title
|
|
1179
|
-
|
|
1180
|
-
|
|
1510
|
+
title,
|
|
1511
|
+
body,
|
|
1512
|
+
url,
|
|
1513
|
+
quoteText,
|
|
1514
|
+
hasAttachments: data.attachments
|
|
1515
|
+
? data.attachments.length > 0
|
|
1516
|
+
: undefined,
|
|
1181
1517
|
});
|
|
1182
1518
|
|
|
1183
|
-
const body = data.bodyMarkdown
|
|
1184
|
-
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1185
|
-
: (data.body ?? null);
|
|
1186
1519
|
const bodyHtml = body ? renderTiptapJson(body) : null;
|
|
1187
|
-
const bodyText = body
|
|
1520
|
+
const bodyText = body
|
|
1521
|
+
? extractBodyText(body, { includeLinkHrefs: true })
|
|
1522
|
+
: null;
|
|
1188
1523
|
|
|
1189
1524
|
// Generate summary for titled notes with body content
|
|
1190
1525
|
let summary: string | null = null;
|
|
1191
|
-
if (format === "note" &&
|
|
1526
|
+
if (format === "note" && title && body && summaryConfig) {
|
|
1192
1527
|
summary = extractSummary(
|
|
1193
1528
|
body,
|
|
1194
1529
|
summaryConfig.maxParagraphs,
|
|
@@ -1201,18 +1536,45 @@ export function createPostService(
|
|
|
1201
1536
|
let status: Status = requestedStatus ?? "published";
|
|
1202
1537
|
let visibility: Visibility | null = requestedVisibility ?? "public";
|
|
1203
1538
|
|
|
1539
|
+
// Collapse the two-input (`flag` + `flagAt`) pair the DTO exposes
|
|
1540
|
+
// into the single timestamp form the DB column stores. Explicit
|
|
1541
|
+
// `*At` wins over the boolean shorthand when both are set.
|
|
1542
|
+
const resolvedPinnedAt =
|
|
1543
|
+
data.pinnedAt !== undefined
|
|
1544
|
+
? data.pinnedAt
|
|
1545
|
+
: data.pinned !== undefined
|
|
1546
|
+
? data.pinned
|
|
1547
|
+
? timestamp
|
|
1548
|
+
: null
|
|
1549
|
+
: null;
|
|
1550
|
+
const resolvedFeaturedAt =
|
|
1551
|
+
data.featuredAt !== undefined
|
|
1552
|
+
? data.featuredAt
|
|
1553
|
+
: data.featured !== undefined
|
|
1554
|
+
? data.featured
|
|
1555
|
+
? timestamp
|
|
1556
|
+
: null
|
|
1557
|
+
: null;
|
|
1558
|
+
|
|
1204
1559
|
if (data.replyToId) {
|
|
1205
1560
|
const parent = await this.getById(data.replyToId);
|
|
1206
1561
|
if (!parent) {
|
|
1207
1562
|
throw new NotFoundError("Parent post");
|
|
1208
1563
|
}
|
|
1209
1564
|
|
|
1210
|
-
if (
|
|
1565
|
+
if (resolvedPinnedAt !== null) {
|
|
1211
1566
|
throw new ConflictError(
|
|
1212
1567
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1213
1568
|
);
|
|
1214
1569
|
}
|
|
1215
1570
|
|
|
1571
|
+
const lastLivePostId = await getLastLivePostIdInThread(parent.threadId);
|
|
1572
|
+
if (lastLivePostId && lastLivePostId !== parent.id) {
|
|
1573
|
+
throw new ConflictError(
|
|
1574
|
+
"This post is no longer the end of the thread. Reply to the latest post instead.",
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1216
1578
|
threadId = parent.threadId;
|
|
1217
1579
|
|
|
1218
1580
|
// Replies inherit visibility from the root at read time.
|
|
@@ -1236,6 +1598,14 @@ export function createPostService(
|
|
|
1236
1598
|
let slug: string;
|
|
1237
1599
|
let aliasPath: string | null = null;
|
|
1238
1600
|
|
|
1601
|
+
// Quote `title` stores source attribution (e.g. author name), not a real
|
|
1602
|
+
// title — deriving the slug from it would produce URLs like `/basho`
|
|
1603
|
+
// that read as "a page about Basho" and collide across every quote from
|
|
1604
|
+
// the same source. Fall through to random IDs instead; users who want a
|
|
1605
|
+
// readable slug can still set one explicitly.
|
|
1606
|
+
const titleForSlug =
|
|
1607
|
+
format === "quote" ? undefined : (title ?? undefined);
|
|
1608
|
+
|
|
1239
1609
|
if (data.path) {
|
|
1240
1610
|
const normalized = normalizePath(data.path);
|
|
1241
1611
|
if (isValidSlug(normalized)) {
|
|
@@ -1250,7 +1620,7 @@ export function createPostService(
|
|
|
1250
1620
|
const slugified = slugify(normalized);
|
|
1251
1621
|
slug = await generatePostSlug({
|
|
1252
1622
|
slug: slugified || undefined,
|
|
1253
|
-
title:
|
|
1623
|
+
title: titleForSlug,
|
|
1254
1624
|
idLength: config.slugIdLength,
|
|
1255
1625
|
isAvailable: isSlugAvailable,
|
|
1256
1626
|
});
|
|
@@ -1263,13 +1633,61 @@ export function createPostService(
|
|
|
1263
1633
|
} else {
|
|
1264
1634
|
slug = await generatePostSlug({
|
|
1265
1635
|
slug: data.slug,
|
|
1266
|
-
title:
|
|
1636
|
+
title: titleForSlug,
|
|
1267
1637
|
idLength: config.slugIdLength,
|
|
1268
1638
|
isAvailable: isSlugAvailable,
|
|
1269
1639
|
});
|
|
1270
1640
|
}
|
|
1271
1641
|
|
|
1272
|
-
|
|
1642
|
+
// When structured `collectionEntries` are provided (Hugo import path),
|
|
1643
|
+
// they win over the bare `collectionIds` slug list and carry
|
|
1644
|
+
// `createdAt` / `position` / `pinnedAt` per row. Otherwise fall back to
|
|
1645
|
+
// the simple list and derive those fields from sensible defaults.
|
|
1646
|
+
const hasCollectionEntries =
|
|
1647
|
+
data.collectionEntries !== undefined &&
|
|
1648
|
+
data.collectionEntries.length > 0;
|
|
1649
|
+
const collectionInsertRows: {
|
|
1650
|
+
siteId: string;
|
|
1651
|
+
postId: string;
|
|
1652
|
+
collectionId: string;
|
|
1653
|
+
createdAt: number;
|
|
1654
|
+
position: number;
|
|
1655
|
+
pinnedAt: number | null;
|
|
1656
|
+
}[] = hasCollectionEntries
|
|
1657
|
+
? (() => {
|
|
1658
|
+
const seen = new Set<string>();
|
|
1659
|
+
const rows: {
|
|
1660
|
+
siteId: string;
|
|
1661
|
+
postId: string;
|
|
1662
|
+
collectionId: string;
|
|
1663
|
+
createdAt: number;
|
|
1664
|
+
position: number;
|
|
1665
|
+
pinnedAt: number | null;
|
|
1666
|
+
}[] = [];
|
|
1667
|
+
let fallbackPosition = 0;
|
|
1668
|
+
for (const entry of data.collectionEntries ?? []) {
|
|
1669
|
+
if (seen.has(entry.collectionId)) continue;
|
|
1670
|
+
seen.add(entry.collectionId);
|
|
1671
|
+
rows.push({
|
|
1672
|
+
siteId,
|
|
1673
|
+
postId: id,
|
|
1674
|
+
collectionId: entry.collectionId,
|
|
1675
|
+
createdAt: entry.createdAt ?? timestamp,
|
|
1676
|
+
position: entry.position ?? fallbackPosition,
|
|
1677
|
+
pinnedAt: entry.pinnedAt ?? null,
|
|
1678
|
+
});
|
|
1679
|
+
fallbackPosition++;
|
|
1680
|
+
}
|
|
1681
|
+
return rows;
|
|
1682
|
+
})()
|
|
1683
|
+
: [...new Set(data.collectionIds ?? [])].map((collectionId, index) => ({
|
|
1684
|
+
siteId,
|
|
1685
|
+
postId: id,
|
|
1686
|
+
collectionId,
|
|
1687
|
+
createdAt: timestamp,
|
|
1688
|
+
position: index,
|
|
1689
|
+
pinnedAt: null,
|
|
1690
|
+
}));
|
|
1273
1691
|
|
|
1274
1692
|
try {
|
|
1275
1693
|
if (usesBatchWrites) {
|
|
@@ -1282,14 +1700,14 @@ export function createPostService(
|
|
|
1282
1700
|
format,
|
|
1283
1701
|
status,
|
|
1284
1702
|
visibility,
|
|
1285
|
-
pinnedAt:
|
|
1286
|
-
featuredAt:
|
|
1287
|
-
title
|
|
1288
|
-
url
|
|
1289
|
-
body
|
|
1703
|
+
pinnedAt: resolvedPinnedAt,
|
|
1704
|
+
featuredAt: resolvedFeaturedAt,
|
|
1705
|
+
title,
|
|
1706
|
+
url,
|
|
1707
|
+
body,
|
|
1290
1708
|
bodyHtml,
|
|
1291
1709
|
bodyText,
|
|
1292
|
-
quoteText
|
|
1710
|
+
quoteText,
|
|
1293
1711
|
summary,
|
|
1294
1712
|
rating,
|
|
1295
1713
|
replyToId: data.replyToId ?? null,
|
|
@@ -1333,16 +1751,9 @@ export function createPostService(
|
|
|
1333
1751
|
);
|
|
1334
1752
|
}
|
|
1335
1753
|
|
|
1336
|
-
if (
|
|
1754
|
+
if (collectionInsertRows.length > 0) {
|
|
1337
1755
|
writeQueries.push(
|
|
1338
|
-
db.insert(postCollections).values(
|
|
1339
|
-
collectionIds.map((collectionId) => ({
|
|
1340
|
-
siteId,
|
|
1341
|
-
postId: id,
|
|
1342
|
-
collectionId,
|
|
1343
|
-
createdAt: timestamp,
|
|
1344
|
-
})),
|
|
1345
|
-
),
|
|
1756
|
+
db.insert(postCollections).values(collectionInsertRows),
|
|
1346
1757
|
);
|
|
1347
1758
|
}
|
|
1348
1759
|
|
|
@@ -1360,14 +1771,14 @@ export function createPostService(
|
|
|
1360
1771
|
format,
|
|
1361
1772
|
status,
|
|
1362
1773
|
visibility,
|
|
1363
|
-
pinnedAt:
|
|
1364
|
-
featuredAt:
|
|
1365
|
-
title
|
|
1366
|
-
url
|
|
1367
|
-
body
|
|
1774
|
+
pinnedAt: resolvedPinnedAt,
|
|
1775
|
+
featuredAt: resolvedFeaturedAt,
|
|
1776
|
+
title,
|
|
1777
|
+
url,
|
|
1778
|
+
body,
|
|
1368
1779
|
bodyHtml,
|
|
1369
1780
|
bodyText,
|
|
1370
|
-
quoteText
|
|
1781
|
+
quoteText,
|
|
1371
1782
|
summary,
|
|
1372
1783
|
rating,
|
|
1373
1784
|
replyToId: data.replyToId ?? null,
|
|
@@ -1406,15 +1817,8 @@ export function createPostService(
|
|
|
1406
1817
|
});
|
|
1407
1818
|
}
|
|
1408
1819
|
|
|
1409
|
-
if (
|
|
1410
|
-
await tx.insert(postCollections).values(
|
|
1411
|
-
collectionIds.map((collectionId) => ({
|
|
1412
|
-
siteId,
|
|
1413
|
-
postId: id,
|
|
1414
|
-
collectionId,
|
|
1415
|
-
createdAt: timestamp,
|
|
1416
|
-
})),
|
|
1417
|
-
);
|
|
1820
|
+
if (collectionInsertRows.length > 0) {
|
|
1821
|
+
await tx.insert(postCollections).values(collectionInsertRows);
|
|
1418
1822
|
}
|
|
1419
1823
|
});
|
|
1420
1824
|
}
|
|
@@ -1434,7 +1838,7 @@ export function createPostService(
|
|
|
1434
1838
|
}
|
|
1435
1839
|
|
|
1436
1840
|
// Bump thread root's lastActivityAt when creating a published reply
|
|
1437
|
-
if (data.replyToId && status === "published") {
|
|
1841
|
+
if (data.replyToId && status === "published" && !data.quietReply) {
|
|
1438
1842
|
await recalculateThreadLastActivity(threadId);
|
|
1439
1843
|
}
|
|
1440
1844
|
|
|
@@ -1447,13 +1851,25 @@ export function createPostService(
|
|
|
1447
1851
|
await createAttachmentMediaIds(attachmentInputs, deps);
|
|
1448
1852
|
|
|
1449
1853
|
try {
|
|
1450
|
-
const post = await this.create(
|
|
1854
|
+
const post = await this.create(
|
|
1855
|
+
{ ...data, attachments: attachmentInputs },
|
|
1856
|
+
summaryConfig,
|
|
1857
|
+
);
|
|
1451
1858
|
|
|
1452
1859
|
try {
|
|
1453
1860
|
if (orderedMediaIds.length > 0) {
|
|
1454
1861
|
await deps.media.attachToPost(post.id, orderedMediaIds);
|
|
1455
1862
|
}
|
|
1456
1863
|
await applyAttachmentAltUpdates(attachmentInputs, deps);
|
|
1864
|
+
|
|
1865
|
+
// Best-effort: fetch and store link preview thumbnail
|
|
1866
|
+
await resolveAndStorePreview(
|
|
1867
|
+
post.id,
|
|
1868
|
+
post.format,
|
|
1869
|
+
post.url,
|
|
1870
|
+
deps.storage,
|
|
1871
|
+
);
|
|
1872
|
+
|
|
1457
1873
|
return post;
|
|
1458
1874
|
} catch (error) {
|
|
1459
1875
|
await deps.media.attachToPost(post.id, []).catch(() => undefined);
|
|
@@ -1470,6 +1886,47 @@ export function createPostService(
|
|
|
1470
1886
|
}
|
|
1471
1887
|
},
|
|
1472
1888
|
|
|
1889
|
+
async createThreadWithAttachments(items, deps, summaryConfig) {
|
|
1890
|
+
if (items.length < 2) {
|
|
1891
|
+
throw new ValidationError("A thread requires at least 2 posts.");
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
const created: Post[] = [];
|
|
1895
|
+
|
|
1896
|
+
for (let i = 0; i < items.length; i++) {
|
|
1897
|
+
const item = items[i];
|
|
1898
|
+
if (!item) continue;
|
|
1899
|
+
const { data, attachments } = item;
|
|
1900
|
+
const prevPost = created[i - 1];
|
|
1901
|
+
const postData: CreatePost = {
|
|
1902
|
+
...data,
|
|
1903
|
+
// Chain each post as a reply to the previous one (server-side chaining)
|
|
1904
|
+
replyToId: i === 0 ? data.replyToId : prevPost?.id,
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
try {
|
|
1908
|
+
const post = await this.createWithAttachments(
|
|
1909
|
+
postData,
|
|
1910
|
+
attachments,
|
|
1911
|
+
deps,
|
|
1912
|
+
summaryConfig,
|
|
1913
|
+
);
|
|
1914
|
+
created.push(post);
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
// Rollback: delete all already-created posts in reverse order
|
|
1917
|
+
for (const p of [...created].reverse()) {
|
|
1918
|
+
await this.delete(p.id, {
|
|
1919
|
+
media: deps.media,
|
|
1920
|
+
storage: deps.storage,
|
|
1921
|
+
}).catch(() => undefined);
|
|
1922
|
+
}
|
|
1923
|
+
throw error;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return created;
|
|
1928
|
+
},
|
|
1929
|
+
|
|
1473
1930
|
async update(id, data, summaryConfig) {
|
|
1474
1931
|
const existing = await this.getById(id);
|
|
1475
1932
|
if (!existing) return null;
|
|
@@ -1479,9 +1936,12 @@ export function createPostService(
|
|
|
1479
1936
|
data.format !== undefined
|
|
1480
1937
|
? ensurePostFormat(data.format)
|
|
1481
1938
|
: existing.format;
|
|
1482
|
-
const nextUrl =
|
|
1939
|
+
const nextUrl =
|
|
1940
|
+
data.url !== undefined ? data.url?.trim() || null : existing.url;
|
|
1483
1941
|
const nextQuoteText =
|
|
1484
|
-
data.quoteText !== undefined
|
|
1942
|
+
data.quoteText !== undefined
|
|
1943
|
+
? data.quoteText?.trim() || null
|
|
1944
|
+
: existing.quoteText;
|
|
1485
1945
|
const nextStatus =
|
|
1486
1946
|
data.status !== undefined
|
|
1487
1947
|
? ensurePostStatus(data.status)
|
|
@@ -1493,11 +1953,26 @@ export function createPostService(
|
|
|
1493
1953
|
const nextRating =
|
|
1494
1954
|
data.rating !== undefined ? ensurePostRating(data.rating) : undefined;
|
|
1495
1955
|
|
|
1956
|
+
const nextTitle =
|
|
1957
|
+
data.title !== undefined ? data.title?.trim() || null : existing.title;
|
|
1958
|
+
|
|
1496
1959
|
assertPostFormatShape({
|
|
1497
1960
|
format: nextFormat,
|
|
1498
|
-
title:
|
|
1961
|
+
title: nextTitle,
|
|
1962
|
+
body:
|
|
1963
|
+
data.body !== undefined || data.bodyMarkdown !== undefined
|
|
1964
|
+
? data.bodyMarkdown
|
|
1965
|
+
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1966
|
+
: (data.body ?? null)
|
|
1967
|
+
: existing.body,
|
|
1499
1968
|
url: nextUrl,
|
|
1500
1969
|
quoteText: nextQuoteText,
|
|
1970
|
+
// During update we can't cheaply resolve final attachment state;
|
|
1971
|
+
// existing posts already passed creation validation, so we only
|
|
1972
|
+
// guard against clearing all text fields here.
|
|
1973
|
+
hasAttachments: data.attachments
|
|
1974
|
+
? data.attachments.length > 0
|
|
1975
|
+
: undefined,
|
|
1501
1976
|
});
|
|
1502
1977
|
assertDraftPublishedAt(nextStatus, data.publishedAt);
|
|
1503
1978
|
|
|
@@ -1520,32 +1995,40 @@ export function createPostService(
|
|
|
1520
1995
|
}
|
|
1521
1996
|
|
|
1522
1997
|
if (data.format !== undefined) updates.format = nextFormat;
|
|
1523
|
-
if (data.title !== undefined) updates.title =
|
|
1524
|
-
if (data.url !== undefined) updates.url =
|
|
1525
|
-
if (data.quoteText !== undefined) updates.quoteText =
|
|
1998
|
+
if (data.title !== undefined) updates.title = nextTitle;
|
|
1999
|
+
if (data.url !== undefined) updates.url = nextUrl;
|
|
2000
|
+
if (data.quoteText !== undefined) updates.quoteText = nextQuoteText;
|
|
1526
2001
|
if (data.rating !== undefined) updates.rating = nextRating;
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
2002
|
+
// Prefer explicit timestamp when provided; fall back to boolean shorthand.
|
|
2003
|
+
if (data.pinnedAt !== undefined) {
|
|
2004
|
+
updates.pinnedAt = data.pinnedAt;
|
|
2005
|
+
} else if (data.pinned !== undefined) {
|
|
2006
|
+
updates.pinnedAt = data.pinned ? timestamp : null;
|
|
2007
|
+
}
|
|
2008
|
+
if (data.featuredAt !== undefined) {
|
|
2009
|
+
updates.featuredAt = data.featuredAt;
|
|
2010
|
+
} else if (data.featured !== undefined) {
|
|
2011
|
+
updates.featuredAt = data.featured ? timestamp : null;
|
|
2012
|
+
}
|
|
1531
2013
|
|
|
1532
2014
|
if (data.body !== undefined || data.bodyMarkdown !== undefined) {
|
|
1533
|
-
const
|
|
2015
|
+
const rawBody = data.bodyMarkdown
|
|
1534
2016
|
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1535
2017
|
: (data.body ?? null);
|
|
2018
|
+
const normalizedBody = rawBody ? trimTiptapBody(rawBody) : null;
|
|
1536
2019
|
updates.body = normalizedBody;
|
|
1537
2020
|
updates.bodyHtml = normalizedBody
|
|
1538
2021
|
? renderTiptapJson(normalizedBody)
|
|
1539
2022
|
: null;
|
|
1540
2023
|
updates.bodyText = normalizedBody
|
|
1541
|
-
? extractBodyText(normalizedBody)
|
|
2024
|
+
? extractBodyText(normalizedBody, { includeLinkHrefs: true })
|
|
1542
2025
|
: null;
|
|
1543
2026
|
}
|
|
1544
2027
|
|
|
1545
2028
|
// Recompute summary when body, title, or format change
|
|
1546
2029
|
if (summaryConfig) {
|
|
1547
2030
|
const format = nextFormat;
|
|
1548
|
-
const title =
|
|
2031
|
+
const title = nextTitle;
|
|
1549
2032
|
const body =
|
|
1550
2033
|
data.bodyMarkdown !== undefined
|
|
1551
2034
|
? data.bodyMarkdown
|
|
@@ -1572,7 +2055,10 @@ export function createPostService(
|
|
|
1572
2055
|
"Cannot change visibility of a thread reply. Update the root post instead.",
|
|
1573
2056
|
);
|
|
1574
2057
|
}
|
|
1575
|
-
if (
|
|
2058
|
+
if (
|
|
2059
|
+
(data.pinnedAt !== undefined && data.pinnedAt !== null) ||
|
|
2060
|
+
data.pinned === true
|
|
2061
|
+
) {
|
|
1576
2062
|
throw new ConflictError(
|
|
1577
2063
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1578
2064
|
);
|
|
@@ -1631,9 +2117,9 @@ export function createPostService(
|
|
|
1631
2117
|
}
|
|
1632
2118
|
|
|
1633
2119
|
// Complex case: cascade + update + collection sync atomically
|
|
1634
|
-
const
|
|
1635
|
-
? await
|
|
1636
|
-
:
|
|
2120
|
+
const existingCollectionTimestamps = needsCollectionSync
|
|
2121
|
+
? await getPostCollectionTimestamps(id)
|
|
2122
|
+
: new Map<string, number>();
|
|
1637
2123
|
let updateResult: (typeof posts.$inferSelect)[] | undefined;
|
|
1638
2124
|
|
|
1639
2125
|
if (usesBatchWrites) {
|
|
@@ -1688,38 +2174,30 @@ export function createPostService(
|
|
|
1688
2174
|
);
|
|
1689
2175
|
|
|
1690
2176
|
if (needsCollectionSync) {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if (removedIds.length > 0) {
|
|
1701
|
-
writeQueries.push(
|
|
1702
|
-
db
|
|
1703
|
-
.delete(postCollections)
|
|
1704
|
-
.where(
|
|
1705
|
-
and(
|
|
1706
|
-
eq(postCollections.siteId, siteId),
|
|
1707
|
-
eq(postCollections.postId, id),
|
|
1708
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1709
|
-
),
|
|
2177
|
+
// Delete all and re-insert to preserve user-specified ordering
|
|
2178
|
+
writeQueries.push(
|
|
2179
|
+
db
|
|
2180
|
+
.delete(postCollections)
|
|
2181
|
+
.where(
|
|
2182
|
+
and(
|
|
2183
|
+
eq(postCollections.siteId, siteId),
|
|
2184
|
+
eq(postCollections.postId, id),
|
|
1710
2185
|
),
|
|
1711
|
-
|
|
1712
|
-
|
|
2186
|
+
),
|
|
2187
|
+
);
|
|
1713
2188
|
|
|
1714
|
-
if (
|
|
2189
|
+
if (nextCollectionIds.length > 0) {
|
|
1715
2190
|
const collectionTimestamp = now();
|
|
1716
2191
|
writeQueries.push(
|
|
1717
2192
|
db.insert(postCollections).values(
|
|
1718
|
-
|
|
2193
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1719
2194
|
siteId,
|
|
1720
2195
|
postId: id,
|
|
1721
2196
|
collectionId,
|
|
1722
|
-
createdAt:
|
|
2197
|
+
createdAt:
|
|
2198
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2199
|
+
collectionTimestamp,
|
|
2200
|
+
position: index,
|
|
1723
2201
|
})),
|
|
1724
2202
|
),
|
|
1725
2203
|
);
|
|
@@ -1779,35 +2257,27 @@ export function createPostService(
|
|
|
1779
2257
|
.returning();
|
|
1780
2258
|
|
|
1781
2259
|
if (needsCollectionSync) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (removedIds.length > 0) {
|
|
1792
|
-
await tx
|
|
1793
|
-
.delete(postCollections)
|
|
1794
|
-
.where(
|
|
1795
|
-
and(
|
|
1796
|
-
eq(postCollections.siteId, siteId),
|
|
1797
|
-
eq(postCollections.postId, id),
|
|
1798
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1799
|
-
),
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
2260
|
+
// Delete all and re-insert to preserve user-specified ordering
|
|
2261
|
+
await tx
|
|
2262
|
+
.delete(postCollections)
|
|
2263
|
+
.where(
|
|
2264
|
+
and(
|
|
2265
|
+
eq(postCollections.siteId, siteId),
|
|
2266
|
+
eq(postCollections.postId, id),
|
|
2267
|
+
),
|
|
2268
|
+
);
|
|
1802
2269
|
|
|
1803
|
-
if (
|
|
2270
|
+
if (nextCollectionIds.length > 0) {
|
|
1804
2271
|
const collectionTimestamp = now();
|
|
1805
2272
|
await tx.insert(postCollections).values(
|
|
1806
|
-
|
|
2273
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1807
2274
|
siteId,
|
|
1808
2275
|
postId: id,
|
|
1809
2276
|
collectionId,
|
|
1810
|
-
createdAt:
|
|
2277
|
+
createdAt:
|
|
2278
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2279
|
+
collectionTimestamp,
|
|
2280
|
+
position: index,
|
|
1811
2281
|
})),
|
|
1812
2282
|
);
|
|
1813
2283
|
}
|
|
@@ -1824,7 +2294,32 @@ export function createPostService(
|
|
|
1824
2294
|
|
|
1825
2295
|
async updateWithAttachments(id, data, attachments, deps, summaryConfig) {
|
|
1826
2296
|
if (attachments === undefined) {
|
|
1827
|
-
|
|
2297
|
+
const existing = await this.getById(id);
|
|
2298
|
+
const post = await this.update(id, data, summaryConfig);
|
|
2299
|
+
if (post && existing) {
|
|
2300
|
+
const urlChanged =
|
|
2301
|
+
data.url !== undefined && data.url !== existing.url;
|
|
2302
|
+
const formatChanged =
|
|
2303
|
+
data.format !== undefined && data.format !== existing.format;
|
|
2304
|
+
if (urlChanged || formatChanged) {
|
|
2305
|
+
await deletePreviewImage(existing.previewImageKey, deps.storage);
|
|
2306
|
+
await db
|
|
2307
|
+
.update(posts)
|
|
2308
|
+
.set({
|
|
2309
|
+
previewImageKey: null,
|
|
2310
|
+
previewKind: null,
|
|
2311
|
+
previewProvider: null,
|
|
2312
|
+
})
|
|
2313
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
|
|
2314
|
+
await resolveAndStorePreview(
|
|
2315
|
+
post.id,
|
|
2316
|
+
post.format,
|
|
2317
|
+
post.url,
|
|
2318
|
+
deps.storage,
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return post;
|
|
1828
2323
|
}
|
|
1829
2324
|
|
|
1830
2325
|
const existingPost = await this.getById(id);
|
|
@@ -1862,17 +2357,41 @@ export function createPostService(
|
|
|
1862
2357
|
await applyAttachmentAltUpdates(attachments, deps);
|
|
1863
2358
|
|
|
1864
2359
|
const nextAttachmentIds = new Set(orderedMediaIds);
|
|
1865
|
-
const
|
|
1866
|
-
.filter(
|
|
1867
|
-
(attachment) =>
|
|
1868
|
-
attachment.mimeType === "text/x-tiptap+json" &&
|
|
1869
|
-
!nextAttachmentIds.has(attachment.id),
|
|
1870
|
-
)
|
|
2360
|
+
const removedAttachmentIds = existingAttachments
|
|
2361
|
+
.filter((attachment) => !nextAttachmentIds.has(attachment.id))
|
|
1871
2362
|
.map((attachment) => attachment.id);
|
|
1872
2363
|
await deps.media
|
|
1873
|
-
.deleteByIds(
|
|
2364
|
+
.deleteByIds(removedAttachmentIds, deps.storage)
|
|
1874
2365
|
.catch(() => undefined);
|
|
1875
2366
|
|
|
2367
|
+
// Best-effort: update link preview when URL changes
|
|
2368
|
+
const urlChanged =
|
|
2369
|
+
data.url !== undefined && data.url !== existingPost.url;
|
|
2370
|
+
const formatChanged =
|
|
2371
|
+
data.format !== undefined && data.format !== existingPost.format;
|
|
2372
|
+
if (urlChanged || formatChanged) {
|
|
2373
|
+
// Clean up old preview image
|
|
2374
|
+
await deletePreviewImage(existingPost.previewImageKey, deps.storage);
|
|
2375
|
+
|
|
2376
|
+
// Clear preview fields first (may be re-set by resolveAndStorePreview)
|
|
2377
|
+
await db
|
|
2378
|
+
.update(posts)
|
|
2379
|
+
.set({
|
|
2380
|
+
previewImageKey: null,
|
|
2381
|
+
previewKind: null,
|
|
2382
|
+
previewProvider: null,
|
|
2383
|
+
})
|
|
2384
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
|
|
2385
|
+
|
|
2386
|
+
// Fetch new preview if applicable
|
|
2387
|
+
await resolveAndStorePreview(
|
|
2388
|
+
post.id,
|
|
2389
|
+
post.format,
|
|
2390
|
+
post.url,
|
|
2391
|
+
deps.storage,
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
1876
2395
|
return post;
|
|
1877
2396
|
} catch (error) {
|
|
1878
2397
|
if (replacedAttachments) {
|
|
@@ -1900,17 +2419,16 @@ export function createPostService(
|
|
|
1900
2419
|
const existing = await this.getById(id);
|
|
1901
2420
|
if (!existing) return false;
|
|
1902
2421
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
const thread = await this.getThread(id);
|
|
1908
|
-
postIds = thread.map((p) => p.id);
|
|
1909
|
-
} else {
|
|
1910
|
-
postIds = [id];
|
|
1911
|
-
}
|
|
2422
|
+
const isRoot = !isThreadReply(existing);
|
|
2423
|
+
const affectedPosts: Post[] = isRoot
|
|
2424
|
+
? await this.getThread(id)
|
|
2425
|
+
: [existing];
|
|
1912
2426
|
|
|
1913
|
-
|
|
2427
|
+
// Clean up media and preview images
|
|
2428
|
+
if (deps?.media) {
|
|
2429
|
+
const mediaMap = await deps.media.getByPostIds(
|
|
2430
|
+
affectedPosts.map((p) => p.id),
|
|
2431
|
+
);
|
|
1914
2432
|
const allMedia = [...mediaMap.values()].flat();
|
|
1915
2433
|
if (allMedia.length > 0) {
|
|
1916
2434
|
await deps.media.deleteByIds(
|
|
@@ -1918,18 +2436,20 @@ export function createPostService(
|
|
|
1918
2436
|
deps.storage,
|
|
1919
2437
|
);
|
|
1920
2438
|
}
|
|
2439
|
+
|
|
2440
|
+
for (const p of affectedPosts) {
|
|
2441
|
+
await deletePreviewImage(p.previewImageKey, deps.storage);
|
|
2442
|
+
}
|
|
1921
2443
|
}
|
|
1922
2444
|
|
|
1923
2445
|
const timestamp = now();
|
|
1924
2446
|
|
|
1925
|
-
|
|
1926
|
-
if (!isThreadReply(existing)) {
|
|
2447
|
+
if (isRoot) {
|
|
1927
2448
|
await db
|
|
1928
2449
|
.update(posts)
|
|
1929
2450
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
1930
2451
|
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
1931
2452
|
} else {
|
|
1932
|
-
// Soft-delete the single reply
|
|
1933
2453
|
await db
|
|
1934
2454
|
.update(posts)
|
|
1935
2455
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
@@ -1940,6 +2460,23 @@ export function createPostService(
|
|
|
1940
2460
|
return true;
|
|
1941
2461
|
},
|
|
1942
2462
|
|
|
2463
|
+
async deleteThreadDraft(id, deps) {
|
|
2464
|
+
const deleted = await this.delete(id, deps);
|
|
2465
|
+
if (!deleted) return false;
|
|
2466
|
+
|
|
2467
|
+
// Release path_registry entries for all posts in the thread so slugs
|
|
2468
|
+
// can be reused by the replacement thread.
|
|
2469
|
+
const threadRows = await db
|
|
2470
|
+
.select({ id: posts.id })
|
|
2471
|
+
.from(posts)
|
|
2472
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
2473
|
+
for (const row of threadRows) {
|
|
2474
|
+
await resolvedPaths.deleteByPostId(row.id);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
return true;
|
|
2478
|
+
},
|
|
2479
|
+
|
|
1943
2480
|
async getThread(rootId) {
|
|
1944
2481
|
const rows = await db
|
|
1945
2482
|
.select()
|
|
@@ -2029,7 +2566,7 @@ export function createPostService(
|
|
|
2029
2566
|
const rows = await db
|
|
2030
2567
|
.select({
|
|
2031
2568
|
threadId: posts.threadId,
|
|
2032
|
-
count: sql<number>`count(*)`.as("count"),
|
|
2569
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
2033
2570
|
})
|
|
2034
2571
|
.from(posts)
|
|
2035
2572
|
.where(
|
|
@@ -2058,10 +2595,10 @@ export function createPostService(
|
|
|
2058
2595
|
id: posts.id,
|
|
2059
2596
|
threadId: posts.threadId,
|
|
2060
2597
|
createdAt: posts.createdAt,
|
|
2061
|
-
previewRank: sql<number>`ROW_NUMBER() OVER (
|
|
2598
|
+
previewRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2062
2599
|
PARTITION BY ${posts.threadId}
|
|
2063
2600
|
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
2064
|
-
)`.as("preview_rank"),
|
|
2601
|
+
) AS INTEGER)`.as("preview_rank"),
|
|
2065
2602
|
})
|
|
2066
2603
|
.from(posts)
|
|
2067
2604
|
.where(
|
|
@@ -2115,14 +2652,17 @@ export function createPostService(
|
|
|
2115
2652
|
.select({
|
|
2116
2653
|
id: posts.id,
|
|
2117
2654
|
threadId: posts.threadId,
|
|
2118
|
-
|
|
2119
|
-
|
|
2655
|
+
firstReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2656
|
+
PARTITION BY ${posts.threadId}
|
|
2657
|
+
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
2658
|
+
) AS INTEGER)`.as("first_reply_rank"),
|
|
2659
|
+
latestReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2120
2660
|
PARTITION BY ${posts.threadId}
|
|
2121
2661
|
ORDER BY ${posts.createdAt} DESC, ${posts.id} DESC
|
|
2122
|
-
)`.as("
|
|
2123
|
-
totalReplyCount: sql<number>`COUNT(*) OVER (
|
|
2662
|
+
) AS INTEGER)`.as("latest_reply_rank"),
|
|
2663
|
+
totalReplyCount: sql<number>`CAST(COUNT(*) OVER (
|
|
2124
2664
|
PARTITION BY ${posts.threadId}
|
|
2125
|
-
)`.as("total_reply_count"),
|
|
2665
|
+
) AS INTEGER)`.as("total_reply_count"),
|
|
2126
2666
|
})
|
|
2127
2667
|
.from(posts)
|
|
2128
2668
|
.where(
|
|
@@ -2136,41 +2676,72 @@ export function createPostService(
|
|
|
2136
2676
|
)
|
|
2137
2677
|
.as("ranked_replies");
|
|
2138
2678
|
|
|
2139
|
-
const
|
|
2679
|
+
const contextRows = await db
|
|
2140
2680
|
.select({
|
|
2141
2681
|
threadId: rankedReplies.threadId,
|
|
2142
|
-
|
|
2143
|
-
|
|
2682
|
+
id: rankedReplies.id,
|
|
2683
|
+
firstReplyRank: rankedReplies.firstReplyRank,
|
|
2684
|
+
latestReplyRank: rankedReplies.latestReplyRank,
|
|
2144
2685
|
totalReplyCount: rankedReplies.totalReplyCount,
|
|
2145
2686
|
})
|
|
2146
2687
|
.from(rankedReplies)
|
|
2147
|
-
.where(
|
|
2688
|
+
.where(
|
|
2689
|
+
or(
|
|
2690
|
+
eq(rankedReplies.firstReplyRank, 1),
|
|
2691
|
+
lte(rankedReplies.latestReplyRank, 2),
|
|
2692
|
+
),
|
|
2693
|
+
);
|
|
2148
2694
|
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2695
|
+
const hydratedPosts = await hydratePostsById(
|
|
2696
|
+
contextRows.map((row) => row.id),
|
|
2697
|
+
);
|
|
2151
2698
|
|
|
2152
|
-
|
|
2153
|
-
|
|
2699
|
+
const contextByThreadId = new Map<
|
|
2700
|
+
string,
|
|
2701
|
+
{
|
|
2702
|
+
secondReplyId: string | null;
|
|
2703
|
+
penultimateReplyId: string | null;
|
|
2704
|
+
latestReplyId: string | null;
|
|
2705
|
+
totalReplyCount: number;
|
|
2154
2706
|
}
|
|
2707
|
+
>();
|
|
2708
|
+
for (const row of contextRows) {
|
|
2709
|
+
const existing = contextByThreadId.get(row.threadId) ?? {
|
|
2710
|
+
secondReplyId: null,
|
|
2711
|
+
penultimateReplyId: null,
|
|
2712
|
+
latestReplyId: null,
|
|
2713
|
+
totalReplyCount: row.totalReplyCount,
|
|
2714
|
+
};
|
|
2155
2715
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2716
|
+
if (row.firstReplyRank === 1) {
|
|
2717
|
+
existing.secondReplyId = row.id;
|
|
2718
|
+
}
|
|
2719
|
+
if (row.latestReplyRank === 2) {
|
|
2720
|
+
existing.penultimateReplyId = row.id;
|
|
2721
|
+
}
|
|
2722
|
+
if (row.latestReplyRank === 1) {
|
|
2723
|
+
existing.latestReplyId = row.id;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
contextByThreadId.set(row.threadId, existing);
|
|
2727
|
+
}
|
|
2159
2728
|
|
|
2160
2729
|
const result = new Map<string, ThreadTimelineContext>();
|
|
2161
|
-
for (const
|
|
2162
|
-
|
|
2163
|
-
if (!latestReply) continue;
|
|
2730
|
+
for (const [threadId, context] of contextByThreadId) {
|
|
2731
|
+
if (!context.latestReplyId) continue;
|
|
2164
2732
|
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
? (hydratedPosts.get(row.latestReplyToId) ?? null)
|
|
2168
|
-
: null;
|
|
2733
|
+
const latestReply = hydratedPosts.get(context.latestReplyId);
|
|
2734
|
+
if (!latestReply) continue;
|
|
2169
2735
|
|
|
2170
|
-
result.set(
|
|
2736
|
+
result.set(threadId, {
|
|
2737
|
+
secondReply: context.secondReplyId
|
|
2738
|
+
? (hydratedPosts.get(context.secondReplyId) ?? null)
|
|
2739
|
+
: null,
|
|
2740
|
+
penultimateReply: context.penultimateReplyId
|
|
2741
|
+
? (hydratedPosts.get(context.penultimateReplyId) ?? null)
|
|
2742
|
+
: null,
|
|
2171
2743
|
latestReply,
|
|
2172
|
-
|
|
2173
|
-
totalReplyCount: row.totalReplyCount,
|
|
2744
|
+
totalReplyCount: context.totalReplyCount,
|
|
2174
2745
|
});
|
|
2175
2746
|
}
|
|
2176
2747
|
|
|
@@ -2185,7 +2756,10 @@ export function createPostService(
|
|
|
2185
2756
|
|
|
2186
2757
|
const rows = await db
|
|
2187
2758
|
.select({
|
|
2188
|
-
count:
|
|
2759
|
+
count:
|
|
2760
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2761
|
+
"count",
|
|
2762
|
+
),
|
|
2189
2763
|
})
|
|
2190
2764
|
.from(posts)
|
|
2191
2765
|
.where(and(...conditions));
|
|
@@ -2241,7 +2815,10 @@ export function createPostService(
|
|
|
2241
2815
|
|
|
2242
2816
|
const rows = await db
|
|
2243
2817
|
.select({
|
|
2244
|
-
count:
|
|
2818
|
+
count:
|
|
2819
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2820
|
+
"count",
|
|
2821
|
+
),
|
|
2245
2822
|
})
|
|
2246
2823
|
.from(posts)
|
|
2247
2824
|
.innerJoin(
|
|
@@ -2272,10 +2849,10 @@ export function createPostService(
|
|
|
2272
2849
|
buildCollectionMembershipCondition(collectionIds),
|
|
2273
2850
|
];
|
|
2274
2851
|
const sortOrder = options.sortOrder ?? "newest";
|
|
2275
|
-
const
|
|
2852
|
+
const publishedAt =
|
|
2276
2853
|
sortOrder === "oldest"
|
|
2277
|
-
? sql<number>`MIN(${
|
|
2278
|
-
: sql<number>`MAX(${
|
|
2854
|
+
? sql<number>`MIN(${posts.publishedAt})`.as("published_at")
|
|
2855
|
+
: sql<number>`MAX(${posts.publishedAt})`.as("published_at");
|
|
2279
2856
|
const ratingPresence = sql<number>`MAX(
|
|
2280
2857
|
CASE
|
|
2281
2858
|
WHEN ${posts.rating} IS NULL THEN 0
|
|
@@ -2286,10 +2863,16 @@ export function createPostService(
|
|
|
2286
2863
|
"rating_value",
|
|
2287
2864
|
);
|
|
2288
2865
|
|
|
2866
|
+
const collectionPinnedAt =
|
|
2867
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2868
|
+
"collection_pinned_at",
|
|
2869
|
+
);
|
|
2870
|
+
|
|
2289
2871
|
const baseQuery = db
|
|
2290
2872
|
.select({
|
|
2291
2873
|
threadId: posts.threadId,
|
|
2292
|
-
|
|
2874
|
+
publishedAt,
|
|
2875
|
+
collectionPinnedAt,
|
|
2293
2876
|
ratingPresence,
|
|
2294
2877
|
ratingValue,
|
|
2295
2878
|
})
|
|
@@ -2306,15 +2889,24 @@ export function createPostService(
|
|
|
2306
2889
|
|
|
2307
2890
|
let query =
|
|
2308
2891
|
sortOrder === "oldest"
|
|
2309
|
-
? baseQuery.orderBy(
|
|
2892
|
+
? baseQuery.orderBy(
|
|
2893
|
+
desc(collectionPinnedAt),
|
|
2894
|
+
asc(publishedAt),
|
|
2895
|
+
asc(posts.threadId),
|
|
2896
|
+
)
|
|
2310
2897
|
: sortOrder === "rating_desc"
|
|
2311
2898
|
? baseQuery.orderBy(
|
|
2899
|
+
desc(collectionPinnedAt),
|
|
2312
2900
|
desc(ratingPresence),
|
|
2313
2901
|
desc(ratingValue),
|
|
2314
|
-
desc(
|
|
2902
|
+
desc(publishedAt),
|
|
2315
2903
|
desc(posts.threadId),
|
|
2316
2904
|
)
|
|
2317
|
-
: baseQuery.orderBy(
|
|
2905
|
+
: baseQuery.orderBy(
|
|
2906
|
+
desc(collectionPinnedAt),
|
|
2907
|
+
desc(publishedAt),
|
|
2908
|
+
desc(posts.threadId),
|
|
2909
|
+
);
|
|
2318
2910
|
|
|
2319
2911
|
if (options.limit !== undefined) {
|
|
2320
2912
|
query = query.limit(options.limit) as typeof query;
|
|
@@ -2339,14 +2931,23 @@ export function createPostService(
|
|
|
2339
2931
|
...buildThreadRootPageConditions(options),
|
|
2340
2932
|
buildCollectionMembershipCondition(collectionIds),
|
|
2341
2933
|
];
|
|
2934
|
+
const publishedAt = sql<number>`MAX(${posts.publishedAt})`.as(
|
|
2935
|
+
"published_at",
|
|
2936
|
+
);
|
|
2342
2937
|
const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
|
|
2343
2938
|
"collected_at",
|
|
2344
2939
|
);
|
|
2940
|
+
const collectionPinnedAt =
|
|
2941
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2942
|
+
"collection_pinned_at",
|
|
2943
|
+
);
|
|
2345
2944
|
|
|
2346
2945
|
let query = db
|
|
2347
2946
|
.select({
|
|
2348
2947
|
threadId: posts.threadId,
|
|
2948
|
+
publishedAt,
|
|
2349
2949
|
collectedAt,
|
|
2950
|
+
collectionPinnedAt,
|
|
2350
2951
|
})
|
|
2351
2952
|
.from(posts)
|
|
2352
2953
|
.innerJoin(
|
|
@@ -2358,7 +2959,11 @@ export function createPostService(
|
|
|
2358
2959
|
)
|
|
2359
2960
|
.where(and(...conditions))
|
|
2360
2961
|
.groupBy(posts.threadId)
|
|
2361
|
-
.orderBy(
|
|
2962
|
+
.orderBy(
|
|
2963
|
+
desc(collectionPinnedAt),
|
|
2964
|
+
desc(publishedAt),
|
|
2965
|
+
desc(posts.threadId),
|
|
2966
|
+
);
|
|
2362
2967
|
|
|
2363
2968
|
if (options.limit !== undefined) {
|
|
2364
2969
|
query = query.limit(options.limit) as typeof query;
|
|
@@ -2504,5 +3109,61 @@ export function createPostService(
|
|
|
2504
3109
|
|
|
2505
3110
|
return rows.map((r) => parseInt(r.year, 10));
|
|
2506
3111
|
},
|
|
3112
|
+
|
|
3113
|
+
async reindexBodyText(options = {}) {
|
|
3114
|
+
const requested = options.limit ?? 50;
|
|
3115
|
+
const limit = Math.min(Math.max(Math.trunc(requested), 1), 500);
|
|
3116
|
+
const cursor = options.cursor;
|
|
3117
|
+
|
|
3118
|
+
const whereConditions = [
|
|
3119
|
+
eq(posts.siteId, siteId),
|
|
3120
|
+
isNull(posts.deletedAt),
|
|
3121
|
+
];
|
|
3122
|
+
if (cursor) whereConditions.push(gt(posts.id, cursor));
|
|
3123
|
+
|
|
3124
|
+
// Fetch one extra row to detect end-of-data without a separate COUNT.
|
|
3125
|
+
const rows = await db
|
|
3126
|
+
.select({
|
|
3127
|
+
id: posts.id,
|
|
3128
|
+
body: posts.body,
|
|
3129
|
+
bodyText: posts.bodyText,
|
|
3130
|
+
})
|
|
3131
|
+
.from(posts)
|
|
3132
|
+
.where(and(...whereConditions))
|
|
3133
|
+
.orderBy(asc(posts.id))
|
|
3134
|
+
.limit(limit + 1);
|
|
3135
|
+
|
|
3136
|
+
const hasMore = rows.length > limit;
|
|
3137
|
+
const batch = hasMore ? rows.slice(0, limit) : rows;
|
|
3138
|
+
|
|
3139
|
+
let updated = 0;
|
|
3140
|
+
let skipped = 0;
|
|
3141
|
+
|
|
3142
|
+
for (const row of batch) {
|
|
3143
|
+
const nextBodyText = row.body
|
|
3144
|
+
? extractBodyText(row.body, { includeLinkHrefs: true })
|
|
3145
|
+
: null;
|
|
3146
|
+
if (nextBodyText === row.bodyText) {
|
|
3147
|
+
skipped++;
|
|
3148
|
+
continue;
|
|
3149
|
+
}
|
|
3150
|
+
await db
|
|
3151
|
+
.update(posts)
|
|
3152
|
+
.set({ bodyText: nextBodyText })
|
|
3153
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, row.id)));
|
|
3154
|
+
updated++;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
const lastRow = batch.at(-1);
|
|
3158
|
+
const lastId = lastRow ? lastRow.id : null;
|
|
3159
|
+
|
|
3160
|
+
return {
|
|
3161
|
+
processed: batch.length,
|
|
3162
|
+
updated,
|
|
3163
|
+
skipped,
|
|
3164
|
+
nextCursor: hasMore ? lastId : null,
|
|
3165
|
+
done: !hasMore,
|
|
3166
|
+
};
|
|
3167
|
+
},
|
|
2507
3168
|
};
|
|
2508
3169
|
}
|