@jant/core 0.3.39 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -33
- 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.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/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 +698 -0
- package/bin/lib/site-snapshot.js +0 -1
- package/dist/app-B5db9mS0.js +5 -0
- package/dist/{app-BfoG98VD.js → app-CwCepTko.js} +23938 -23014
- 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-BRFl5zQA.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-wCpMcNXs.js +252 -0
- package/dist/github-api-CficQztC.js +176 -0
- package/dist/github-app-F4qZ05xk.js +275 -0
- package/dist/github-sync-zohnA9qv.js +4696 -0
- package/dist/index.js +88 -2
- package/dist/node.js +71 -30
- package/dist/url-FvvgARU9.js +305 -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/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 +65 -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} +145 -8
- 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 -15
- 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__/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 +77 -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/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/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/_journal.json +92 -1
- 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/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/_journal.json +92 -1
- package/src/db/pg/__tests__/node.test.ts +38 -0
- package/src/db/pg/node.ts +149 -0
- package/src/db/pg/schema.ts +130 -12
- package/src/db/rehearsal-fixtures/demo-current.sql +47 -3
- package/src/db/schema.ts +139 -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} +298 -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 -5
- package/src/lib/__tests__/collection-groups.test.ts +80 -0
- package/src/lib/__tests__/constants.test.ts +1 -1
- 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 +11 -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 +148 -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__/resolve-config.test.ts +30 -25
- package/src/lib/__tests__/schemas.test.ts +35 -0
- package/src/lib/__tests__/slug-format.test.ts +8 -0
- package/src/lib/__tests__/summary.test.ts +400 -1
- package/src/lib/__tests__/theme.test.ts +41 -2
- package/src/lib/__tests__/timeline.test.ts +114 -26
- 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 +115 -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 +70 -0
- package/src/lib/collection-paths.ts +40 -0
- package/src/lib/constants.ts +3 -4
- 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 +89 -66
- 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-trigger.ts +199 -0
- package/src/lib/github-sync-worker.ts +72 -0
- package/src/lib/hosted-signin.ts +1 -1
- 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 +40 -8
- package/src/lib/post-display.ts +10 -22
- package/src/lib/public-storage.ts +6 -1
- package/src/lib/render.tsx +5 -2
- package/src/lib/resolve-config.ts +23 -11
- package/src/lib/rich-image.ts +89 -0
- package/src/lib/schemas.ts +186 -12
- package/src/lib/slug-format.ts +8 -0
- package/src/lib/summary.ts +125 -18
- package/src/lib/theme.ts +119 -0
- package/src/lib/timeline.ts +91 -63
- 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/version.ts +18 -0
- package/src/lib/video-playback.ts +73 -0
- package/src/lib/view.ts +130 -90
- 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__/onboarding.test.ts +3 -3
- package/src/middleware/__tests__/secure-headers.test.ts +28 -1
- package/src/middleware/config.ts +7 -10
- package/src/middleware/cors.ts +49 -0
- package/src/middleware/secure-headers.ts +45 -55
- 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 +3 -1
- 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__/settings.test.ts +99 -3
- package/src/routes/api/__tests__/upload-multipart.test.ts +10 -2
- 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 +445 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +45 -2
- 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 +1 -47
- 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/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 +34 -24
- 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 +1163 -70
- package/src/routes/feed/__tests__/{rss.test.ts → feed.test.ts} +62 -123
- package/src/routes/feed/feed.ts +386 -0
- package/src/routes/feed/manifest.ts +67 -0
- package/src/routes/feed/sitemap.ts +11 -2
- 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/archive.tsx +523 -127
- package/src/routes/pages/collection.tsx +265 -59
- package/src/routes/pages/collections.tsx +5 -1
- package/src/routes/pages/home.tsx +2 -2
- package/src/routes/pages/latest.tsx +3 -3
- package/src/routes/pages/page.tsx +221 -3
- package/src/routes/pages/partials.tsx +5 -0
- 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/__tests__/readiness.test.ts +89 -0
- package/src/runtime/readiness.ts +129 -0
- package/src/runtime/site.ts +9 -1
- package/src/services/__tests__/collection.test.ts +181 -53
- 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 +85 -17
- package/src/services/__tests__/post.test.ts +118 -10
- 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 +644 -191
- 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 +1365 -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 +779 -207
- 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 +4217 -1869
- package/src/types/bindings.ts +3 -1
- package/src/types/config.ts +139 -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 +12 -3
- 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 +221 -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 +201 -61
- 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 +232 -37
- 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 -3251
- 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 -803
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/zh-Hans.po +0 -3183
- package/src/i18n/locales/zh-Hans.ts +0 -1
- package/src/i18n/locales/zh-Hant.po +0 -3183
- 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,
|
|
@@ -31,9 +32,10 @@ import {
|
|
|
31
32
|
} from "../db/schema-bundle.js";
|
|
32
33
|
import { createEntityId } from "../lib/ids.js";
|
|
33
34
|
import { now } from "../lib/time.js";
|
|
34
|
-
import { renderTiptapJson } from "../lib/tiptap-render.js";
|
|
35
|
+
import { renderTiptapJson, trimTiptapBody } from "../lib/tiptap-render.js";
|
|
35
36
|
import { extractSummary, extractBodyText } from "../lib/summary.js";
|
|
36
37
|
import { markdownToTiptapJson } from "../lib/markdown-to-tiptap.js";
|
|
38
|
+
import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
|
|
37
39
|
import { generatePostSlug } from "../lib/slug.js";
|
|
38
40
|
import { getSlugValidationIssue } from "../lib/slug-format.js";
|
|
39
41
|
import { normalizePath, slugify } from "../lib/url.js";
|
|
@@ -64,6 +66,12 @@ import {
|
|
|
64
66
|
NotFoundError,
|
|
65
67
|
} from "../lib/errors.js";
|
|
66
68
|
import { createPathService, type PathService } from "./path.js";
|
|
69
|
+
import {
|
|
70
|
+
extractYouTubeVideoId,
|
|
71
|
+
getYouTubeThumbnailUrls,
|
|
72
|
+
} from "../lib/youtube.js";
|
|
73
|
+
import { getPreviewStorageKey } from "../lib/upload.js";
|
|
74
|
+
import { generateRandomId } from "../lib/nanoid.js";
|
|
67
75
|
|
|
68
76
|
/** Dependencies for operations that coordinate with other services */
|
|
69
77
|
export interface PostDeleteDeps {
|
|
@@ -83,6 +91,7 @@ export interface PostFilters {
|
|
|
83
91
|
pinned?: boolean;
|
|
84
92
|
featured?: boolean;
|
|
85
93
|
collectionId?: string;
|
|
94
|
+
collectionIds?: string[];
|
|
86
95
|
/** Exclude posts that are replies (have replyToId set) */
|
|
87
96
|
excludeReplies?: boolean;
|
|
88
97
|
/** Exclude posts hidden from Latest from results */
|
|
@@ -138,8 +147,18 @@ export interface CollectionFeedEntry {
|
|
|
138
147
|
collectedAt: number;
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
export interface PostBodyContent {
|
|
151
|
+
id: string;
|
|
152
|
+
type: "post";
|
|
153
|
+
format: Format;
|
|
154
|
+
contentFormat: "markdown";
|
|
155
|
+
content: string;
|
|
156
|
+
chars: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
export interface PostService {
|
|
142
160
|
getById(id: string): Promise<Post | null>;
|
|
161
|
+
getBodyContent(id: string): Promise<PostBodyContent | null>;
|
|
143
162
|
getBySlug(slug: string): Promise<Post | null>;
|
|
144
163
|
suggestSlug(input: {
|
|
145
164
|
title?: string;
|
|
@@ -150,6 +169,8 @@ export interface PostService {
|
|
|
150
169
|
list(filters?: PostFilters): Promise<Post[]>;
|
|
151
170
|
/** Count posts matching filters (ignores cursor, offset, limit) */
|
|
152
171
|
count(filters?: PostFilters): Promise<number>;
|
|
172
|
+
/** Count posts matching filters up to a fixed limit (ignores cursor, offset, limit) */
|
|
173
|
+
countUpTo(filters: PostFilters | undefined, limit: number): Promise<number>;
|
|
153
174
|
/** Count posts grouped by published year-month (YYYY-MM) */
|
|
154
175
|
countByYearMonth(
|
|
155
176
|
filters?: PostFilters,
|
|
@@ -161,6 +182,24 @@ export interface PostService {
|
|
|
161
182
|
deps: PostAttachmentDeps,
|
|
162
183
|
summaryConfig?: SummaryConfig,
|
|
163
184
|
): Promise<Post>;
|
|
185
|
+
/**
|
|
186
|
+
* Atomically create a thread of posts. The first item is the root; each
|
|
187
|
+
* subsequent item is automatically chained as a reply to the previous one.
|
|
188
|
+
* On failure, all already-created posts are rolled back.
|
|
189
|
+
*
|
|
190
|
+
* @param items - Ordered list of (data, attachments) pairs; at least 2 required
|
|
191
|
+
* @param deps - Media/storage dependencies
|
|
192
|
+
* @param summaryConfig - Optional summary extraction config
|
|
193
|
+
* @returns Ordered list of created posts; posts[0] is the root
|
|
194
|
+
*/
|
|
195
|
+
createThreadWithAttachments(
|
|
196
|
+
items: Array<{
|
|
197
|
+
data: CreatePost;
|
|
198
|
+
attachments: PostAttachmentInput[] | undefined;
|
|
199
|
+
}>,
|
|
200
|
+
deps: PostAttachmentDeps,
|
|
201
|
+
summaryConfig?: SummaryConfig,
|
|
202
|
+
): Promise<Post[]>;
|
|
164
203
|
update(
|
|
165
204
|
id: string,
|
|
166
205
|
data: UpdatePost,
|
|
@@ -181,6 +220,11 @@ export interface PostService {
|
|
|
181
220
|
* @param deps - Media service and optional storage driver for file cleanup
|
|
182
221
|
*/
|
|
183
222
|
delete(id: string, deps?: PostDeleteDeps): Promise<boolean>;
|
|
223
|
+
/**
|
|
224
|
+
* Delete a thread draft and release its slug paths so they can be reused.
|
|
225
|
+
* Used when replacing a saved thread draft with a new version.
|
|
226
|
+
*/
|
|
227
|
+
deleteThreadDraft(id: string, deps?: PostDeleteDeps): Promise<boolean>;
|
|
184
228
|
getThread(rootId: string): Promise<Post[]>;
|
|
185
229
|
updateThreadStatusAndVisibility(
|
|
186
230
|
rootId: string,
|
|
@@ -207,16 +251,31 @@ export interface PostService {
|
|
|
207
251
|
collectionId: string,
|
|
208
252
|
options?: ThreadRootPageOptions,
|
|
209
253
|
): Promise<number>;
|
|
254
|
+
/** Count distinct thread roots that contain published posts in any of the given collections */
|
|
255
|
+
countCollectionThreadRootsForCollections(
|
|
256
|
+
collectionIds: string[],
|
|
257
|
+
options?: ThreadRootPageOptions,
|
|
258
|
+
): Promise<number>;
|
|
210
259
|
/** List collection thread root IDs ordered by collected-at or rating semantics */
|
|
211
260
|
listCollectionThreadRootIds(
|
|
212
261
|
collectionId: string,
|
|
213
262
|
options?: CollectionThreadRootPageOptions,
|
|
214
263
|
): Promise<string[]>;
|
|
264
|
+
/** List collection thread root IDs for a union of collections */
|
|
265
|
+
listCollectionThreadRootIdsForCollections(
|
|
266
|
+
collectionIds: string[],
|
|
267
|
+
options?: CollectionThreadRootPageOptions,
|
|
268
|
+
): Promise<string[]>;
|
|
215
269
|
/** List collection feed entries ordered by latest added-at timestamp */
|
|
216
270
|
listCollectionFeedEntries(
|
|
217
271
|
collectionId: string,
|
|
218
272
|
options?: ThreadRootPageOptions,
|
|
219
273
|
): Promise<CollectionFeedEntry[]>;
|
|
274
|
+
/** List collection feed entries for a union of collections */
|
|
275
|
+
listCollectionFeedEntriesForCollections(
|
|
276
|
+
collectionIds: string[],
|
|
277
|
+
options?: ThreadRootPageOptions,
|
|
278
|
+
): Promise<CollectionFeedEntry[]>;
|
|
220
279
|
/** Fetch all published, non-deleted posts for each requested thread root */
|
|
221
280
|
getPublishedThreads(rootIds: string[]): Promise<Map<string, Post[]>>;
|
|
222
281
|
/** For each thread, return post IDs that belong to the given collection */
|
|
@@ -224,6 +283,11 @@ export interface PostService {
|
|
|
224
283
|
collectionId: string,
|
|
225
284
|
threadIds: string[],
|
|
226
285
|
): Promise<Map<string, string[]>>;
|
|
286
|
+
/** For each thread, return post IDs that belong to any of the given collections */
|
|
287
|
+
getCollectionPostIdsByThreadForCollections(
|
|
288
|
+
collectionIds: string[],
|
|
289
|
+
threadIds: string[],
|
|
290
|
+
): Promise<Map<string, string[]>>;
|
|
227
291
|
/** Get distinct years that have published posts */
|
|
228
292
|
getDistinctYears(filters?: PostFilters): Promise<number[]>;
|
|
229
293
|
/** For each thread ID, return the ID of the last published, non-deleted post */
|
|
@@ -236,24 +300,8 @@ function isValidSlug(value: string): boolean {
|
|
|
236
300
|
return SLUG_RE.test(value);
|
|
237
301
|
}
|
|
238
302
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
let current: unknown = err;
|
|
242
|
-
while (current) {
|
|
243
|
-
const msg = String(current);
|
|
244
|
-
if (
|
|
245
|
-
msg.includes("UNIQUE constraint") ||
|
|
246
|
-
msg.includes("SQLITE_CONSTRAINT")
|
|
247
|
-
) {
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
current =
|
|
251
|
-
current instanceof Error && current.cause !== current
|
|
252
|
-
? current.cause
|
|
253
|
-
: undefined;
|
|
254
|
-
}
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
303
|
+
// Re-export shared constraint detection — see db/dialect.ts
|
|
304
|
+
import { isUniqueConstraintError } from "../db/dialect.js";
|
|
257
305
|
|
|
258
306
|
function hasNonEmptyText(value: string | null | undefined): boolean {
|
|
259
307
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -326,8 +374,10 @@ function ensurePostRating(
|
|
|
326
374
|
function assertPostFormatShape(data: {
|
|
327
375
|
format: Format;
|
|
328
376
|
title?: string | null;
|
|
377
|
+
body?: unknown;
|
|
329
378
|
url?: string | null;
|
|
330
379
|
quoteText?: string | null;
|
|
380
|
+
hasAttachments?: boolean;
|
|
331
381
|
}): void {
|
|
332
382
|
const hasTitle = hasNonEmptyText(data.title);
|
|
333
383
|
const hasUrl = hasNonEmptyText(data.url);
|
|
@@ -340,6 +390,18 @@ function assertPostFormatShape(data: {
|
|
|
340
390
|
if (hasQuoteText) {
|
|
341
391
|
throw new ValidationError("Notes can't include quoted text.");
|
|
342
392
|
}
|
|
393
|
+
// hasAttachments === undefined means unknown (e.g. update without
|
|
394
|
+
// attachment changes) — skip the empty-content check in that case.
|
|
395
|
+
if (
|
|
396
|
+
!hasTitle &&
|
|
397
|
+
!data.body &&
|
|
398
|
+
data.hasAttachments !== undefined &&
|
|
399
|
+
!data.hasAttachments
|
|
400
|
+
) {
|
|
401
|
+
throw new ValidationError(
|
|
402
|
+
"Notes need a title, body, or at least one attachment.",
|
|
403
|
+
);
|
|
404
|
+
}
|
|
343
405
|
return;
|
|
344
406
|
}
|
|
345
407
|
|
|
@@ -470,6 +532,124 @@ export function createPostService(
|
|
|
470
532
|
.where(and(eq(posts.siteId, siteId), eq(posts.id, rootId)));
|
|
471
533
|
}
|
|
472
534
|
|
|
535
|
+
function normalizeCollectionIds(collectionIds: readonly string[]): string[] {
|
|
536
|
+
return [...new Set(collectionIds)];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Fetch a link preview thumbnail and store it.
|
|
541
|
+
* Silently returns without updating if the URL is not a recognized provider
|
|
542
|
+
* or if the fetch fails.
|
|
543
|
+
*/
|
|
544
|
+
async function resolveAndStorePreview(
|
|
545
|
+
postId: string,
|
|
546
|
+
format: string,
|
|
547
|
+
url: string | null | undefined,
|
|
548
|
+
storage: StorageDriver | null | undefined,
|
|
549
|
+
): Promise<void> {
|
|
550
|
+
if (!storage || format !== "link" || !url) return;
|
|
551
|
+
|
|
552
|
+
const videoId = extractYouTubeVideoId(url);
|
|
553
|
+
if (!videoId) return;
|
|
554
|
+
|
|
555
|
+
const thumbnailUrls = getYouTubeThumbnailUrls(videoId);
|
|
556
|
+
let imageBytes: Uint8Array | null = null;
|
|
557
|
+
let contentType = "image/jpeg";
|
|
558
|
+
|
|
559
|
+
for (const thumbUrl of thumbnailUrls) {
|
|
560
|
+
try {
|
|
561
|
+
const response = await fetch(thumbUrl);
|
|
562
|
+
if (response.ok) {
|
|
563
|
+
const ct = response.headers.get("content-type");
|
|
564
|
+
// YouTube returns a grey placeholder (120×90) when maxresdefault
|
|
565
|
+
// is not available. Detect it by checking content-length.
|
|
566
|
+
const cl = response.headers.get("content-length");
|
|
567
|
+
if (cl && parseInt(cl, 10) < 5000) {
|
|
568
|
+
// Too small — likely a placeholder, try next quality
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
imageBytes = new Uint8Array(await response.arrayBuffer());
|
|
572
|
+
if (ct) contentType = ct;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
} catch {
|
|
576
|
+
// Network failure — try next URL
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!imageBytes) return;
|
|
581
|
+
|
|
582
|
+
const ext = contentType.includes("webp")
|
|
583
|
+
? "webp"
|
|
584
|
+
: contentType.includes("png")
|
|
585
|
+
? "png"
|
|
586
|
+
: "jpg";
|
|
587
|
+
const suffix = generateRandomId(8);
|
|
588
|
+
const storageKey = getPreviewStorageKey(siteId, postId, suffix, ext);
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
await storage.put(storageKey, imageBytes, { contentType });
|
|
592
|
+
await db
|
|
593
|
+
.update(posts)
|
|
594
|
+
.set({
|
|
595
|
+
previewImageKey: storageKey,
|
|
596
|
+
previewKind: "video",
|
|
597
|
+
previewProvider: "youtube",
|
|
598
|
+
})
|
|
599
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, postId)));
|
|
600
|
+
} catch {
|
|
601
|
+
// Storage or DB failure — preview is best-effort
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Delete a preview image from storage if it exists.
|
|
607
|
+
*/
|
|
608
|
+
async function deletePreviewImage(
|
|
609
|
+
previewImageKey: string | null | undefined,
|
|
610
|
+
storage: StorageDriver | null | undefined,
|
|
611
|
+
): Promise<void> {
|
|
612
|
+
if (!storage || !previewImageKey) return;
|
|
613
|
+
try {
|
|
614
|
+
await storage.delete(previewImageKey);
|
|
615
|
+
} catch {
|
|
616
|
+
// Best-effort cleanup
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function buildCollectionMembershipCondition(
|
|
621
|
+
collectionIds: readonly string[],
|
|
622
|
+
): SQL<unknown> {
|
|
623
|
+
const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
|
|
624
|
+
const firstCollectionId = uniqueCollectionIds[0];
|
|
625
|
+
if (!firstCollectionId) {
|
|
626
|
+
return sql`0 = 1`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return uniqueCollectionIds.length === 1
|
|
630
|
+
? eq(postCollections.collectionId, firstCollectionId)
|
|
631
|
+
: inArray(postCollections.collectionId, uniqueCollectionIds);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function buildPostCollectionSubqueryCondition(
|
|
635
|
+
collectionIds: readonly string[],
|
|
636
|
+
): SQL<unknown> {
|
|
637
|
+
const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
|
|
638
|
+
if (uniqueCollectionIds.length === 0) {
|
|
639
|
+
return sql`0 = 1`;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const placeholders = uniqueCollectionIds.map(
|
|
643
|
+
(collectionId) => sql`${collectionId}`,
|
|
644
|
+
);
|
|
645
|
+
return sql`${posts.id} IN (
|
|
646
|
+
SELECT post_id
|
|
647
|
+
FROM post_collection
|
|
648
|
+
WHERE site_id = ${siteId}
|
|
649
|
+
AND collection_id IN (${sql.join(placeholders, sql`, `)})
|
|
650
|
+
)`;
|
|
651
|
+
}
|
|
652
|
+
|
|
473
653
|
/** Build WHERE conditions from filters (shared by list and count) */
|
|
474
654
|
function buildFilterConditions(filters: PostFilters) {
|
|
475
655
|
const conditions = [eq(posts.siteId, siteId)];
|
|
@@ -503,15 +683,13 @@ export function createPostService(
|
|
|
503
683
|
if (filters.format) {
|
|
504
684
|
conditions.push(eq(posts.format, filters.format));
|
|
505
685
|
}
|
|
506
|
-
if (filters.
|
|
507
|
-
// Filter by collection via junction table
|
|
686
|
+
if (filters.collectionIds !== undefined) {
|
|
508
687
|
conditions.push(
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
)`,
|
|
688
|
+
buildPostCollectionSubqueryCondition(filters.collectionIds),
|
|
689
|
+
);
|
|
690
|
+
} else if (filters.collectionId !== undefined) {
|
|
691
|
+
conditions.push(
|
|
692
|
+
buildPostCollectionSubqueryCondition([filters.collectionId]),
|
|
515
693
|
);
|
|
516
694
|
}
|
|
517
695
|
if (filters.threadId) {
|
|
@@ -573,6 +751,25 @@ export function createPostService(
|
|
|
573
751
|
return conditions;
|
|
574
752
|
}
|
|
575
753
|
|
|
754
|
+
async function getLastLivePostIdInThread(
|
|
755
|
+
threadId: string,
|
|
756
|
+
): Promise<string | null> {
|
|
757
|
+
const rows = await db
|
|
758
|
+
.select({ id: posts.id })
|
|
759
|
+
.from(posts)
|
|
760
|
+
.where(
|
|
761
|
+
and(
|
|
762
|
+
eq(posts.siteId, siteId),
|
|
763
|
+
eq(posts.threadId, threadId),
|
|
764
|
+
isNull(posts.deletedAt),
|
|
765
|
+
),
|
|
766
|
+
)
|
|
767
|
+
.orderBy(desc(posts.createdAt), desc(posts.id))
|
|
768
|
+
.limit(1);
|
|
769
|
+
|
|
770
|
+
return rows[0]?.id ?? null;
|
|
771
|
+
}
|
|
772
|
+
|
|
576
773
|
function getCursorSortTimestamp(row: typeof posts.$inferSelect): number {
|
|
577
774
|
return row.status === "draft" ? row.updatedAt : (row.lastActivityAt ?? -1);
|
|
578
775
|
}
|
|
@@ -732,6 +929,9 @@ export function createPostService(
|
|
|
732
929
|
quoteText: row.quoteText,
|
|
733
930
|
summary: row.summary,
|
|
734
931
|
rating: ensurePostRating(row.rating, Error),
|
|
932
|
+
previewImageKey: row.previewImageKey,
|
|
933
|
+
previewKind: row.previewKind,
|
|
934
|
+
previewProvider: row.previewProvider,
|
|
735
935
|
replyToId: row.replyToId,
|
|
736
936
|
threadId: row.threadId,
|
|
737
937
|
deletedAt: row.deletedAt,
|
|
@@ -923,6 +1123,24 @@ export function createPostService(
|
|
|
923
1123
|
return rows.map((row) => row.collectionId);
|
|
924
1124
|
}
|
|
925
1125
|
|
|
1126
|
+
async function getPostCollectionTimestamps(
|
|
1127
|
+
postId: string,
|
|
1128
|
+
): Promise<Map<string, number>> {
|
|
1129
|
+
const rows = await db
|
|
1130
|
+
.select({
|
|
1131
|
+
collectionId: postCollections.collectionId,
|
|
1132
|
+
createdAt: postCollections.createdAt,
|
|
1133
|
+
})
|
|
1134
|
+
.from(postCollections)
|
|
1135
|
+
.where(
|
|
1136
|
+
and(
|
|
1137
|
+
eq(postCollections.siteId, siteId),
|
|
1138
|
+
eq(postCollections.postId, postId),
|
|
1139
|
+
),
|
|
1140
|
+
);
|
|
1141
|
+
return new Map(rows.map((r) => [r.collectionId, r.createdAt]));
|
|
1142
|
+
}
|
|
1143
|
+
|
|
926
1144
|
function buildRollbackUpdate(
|
|
927
1145
|
post: Post,
|
|
928
1146
|
collectionIds: string[],
|
|
@@ -960,6 +1178,20 @@ export function createPostService(
|
|
|
960
1178
|
return hydratePost(result[0]);
|
|
961
1179
|
},
|
|
962
1180
|
|
|
1181
|
+
async getBodyContent(id) {
|
|
1182
|
+
const post = await this.getById(id);
|
|
1183
|
+
if (!post) return null;
|
|
1184
|
+
|
|
1185
|
+
return {
|
|
1186
|
+
id: post.id,
|
|
1187
|
+
type: "post",
|
|
1188
|
+
format: post.format,
|
|
1189
|
+
contentFormat: "markdown",
|
|
1190
|
+
content: post.body ? tiptapJsonToMarkdown(post.body) : "",
|
|
1191
|
+
chars: post.bodyText?.length ?? 0,
|
|
1192
|
+
};
|
|
1193
|
+
},
|
|
1194
|
+
|
|
963
1195
|
async getBySlug(slug) {
|
|
964
1196
|
const resolved = await resolvedPaths.resolve(slug);
|
|
965
1197
|
if (!resolved || resolved.kind !== "slug" || !resolved.postId) {
|
|
@@ -1015,6 +1247,14 @@ export function createPostService(
|
|
|
1015
1247
|
ELSE 1
|
|
1016
1248
|
END`;
|
|
1017
1249
|
|
|
1250
|
+
// NULL-sort order differs between dialects: SQLite puts NULLs last for
|
|
1251
|
+
// DESC, Postgres puts them first. Wrap nullable sort keys in COALESCE
|
|
1252
|
+
// so pinned/featured posts land on top under both engines. Mirrors the
|
|
1253
|
+
// expressions used by `buildListCursorCondition` above.
|
|
1254
|
+
const pinnedSortExpr = sql<number>`coalesce(${posts.pinnedAt}, -1)`;
|
|
1255
|
+
const featuredSortExpr = sql<number>`coalesce(${posts.featuredAt}, -1)`;
|
|
1256
|
+
const sortTimestampSortExpr = sql<number>`coalesce(${sortTimestamp}, -1)`;
|
|
1257
|
+
|
|
1018
1258
|
const baseQuery = db
|
|
1019
1259
|
.select()
|
|
1020
1260
|
.from(posts)
|
|
@@ -1024,29 +1264,31 @@ export function createPostService(
|
|
|
1024
1264
|
let query =
|
|
1025
1265
|
filters.featured || filters.sortOrder === undefined
|
|
1026
1266
|
? baseQuery.orderBy(
|
|
1027
|
-
desc(
|
|
1028
|
-
filters.featured
|
|
1267
|
+
desc(pinnedSortExpr),
|
|
1268
|
+
filters.featured
|
|
1269
|
+
? desc(featuredSortExpr)
|
|
1270
|
+
: desc(sortTimestampSortExpr),
|
|
1029
1271
|
desc(posts.id),
|
|
1030
1272
|
)
|
|
1031
1273
|
: filters.sortOrder === "oldest"
|
|
1032
1274
|
? baseQuery.orderBy(
|
|
1033
|
-
desc(
|
|
1034
|
-
asc(
|
|
1275
|
+
desc(pinnedSortExpr),
|
|
1276
|
+
asc(sortTimestampSortExpr),
|
|
1035
1277
|
asc(posts.id),
|
|
1036
1278
|
)
|
|
1037
1279
|
: filters.sortOrder === "rating_desc"
|
|
1038
1280
|
? baseQuery.orderBy(
|
|
1039
|
-
desc(
|
|
1281
|
+
desc(pinnedSortExpr),
|
|
1040
1282
|
desc(ratingPresence),
|
|
1041
1283
|
desc(posts.rating),
|
|
1042
|
-
desc(
|
|
1284
|
+
desc(sortTimestampSortExpr),
|
|
1043
1285
|
desc(posts.id),
|
|
1044
1286
|
)
|
|
1045
1287
|
: baseQuery.orderBy(
|
|
1046
|
-
desc(
|
|
1288
|
+
desc(pinnedSortExpr),
|
|
1047
1289
|
desc(ratingPresence),
|
|
1048
1290
|
asc(posts.rating),
|
|
1049
|
-
desc(
|
|
1291
|
+
desc(sortTimestampSortExpr),
|
|
1050
1292
|
desc(posts.id),
|
|
1051
1293
|
);
|
|
1052
1294
|
|
|
@@ -1062,13 +1304,29 @@ export function createPostService(
|
|
|
1062
1304
|
const conditions = buildFilterConditions(filters);
|
|
1063
1305
|
|
|
1064
1306
|
const result = await db
|
|
1065
|
-
.select({ count: sql<number>`count(*)`.as("count") })
|
|
1307
|
+
.select({ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count") })
|
|
1066
1308
|
.from(posts)
|
|
1067
1309
|
.where(conditions.length > 0 ? and(...conditions) : undefined);
|
|
1068
1310
|
|
|
1069
1311
|
return result[0]?.count ?? 0;
|
|
1070
1312
|
},
|
|
1071
1313
|
|
|
1314
|
+
async countUpTo(filters = {}, limit) {
|
|
1315
|
+
const normalizedLimit = Math.max(0, Math.trunc(limit));
|
|
1316
|
+
if (normalizedLimit === 0) {
|
|
1317
|
+
return 0;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const conditions = buildFilterConditions(filters);
|
|
1321
|
+
const rows = await db
|
|
1322
|
+
.select({ id: posts.id })
|
|
1323
|
+
.from(posts)
|
|
1324
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1325
|
+
.limit(normalizedLimit);
|
|
1326
|
+
|
|
1327
|
+
return rows.length;
|
|
1328
|
+
},
|
|
1329
|
+
|
|
1072
1330
|
async countByYearMonth(filters = {}) {
|
|
1073
1331
|
const conditions = [
|
|
1074
1332
|
...buildFilterConditions(filters),
|
|
@@ -1079,7 +1337,7 @@ export function createPostService(
|
|
|
1079
1337
|
return db
|
|
1080
1338
|
.select({
|
|
1081
1339
|
yearMonth: publishedYearMonthExpr.as("year_month"),
|
|
1082
|
-
count: sql<number>`count(*)`.as("count"),
|
|
1340
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
1083
1341
|
})
|
|
1084
1342
|
.from(posts)
|
|
1085
1343
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
@@ -1099,22 +1357,31 @@ export function createPostService(
|
|
|
1099
1357
|
: undefined;
|
|
1100
1358
|
const rating = ensurePostRating(data.rating);
|
|
1101
1359
|
|
|
1360
|
+
const rawBody = data.bodyMarkdown
|
|
1361
|
+
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1362
|
+
: (data.body ?? null);
|
|
1363
|
+
const body = rawBody ? trimTiptapBody(rawBody) : null;
|
|
1364
|
+
const title = data.title?.trim() || null;
|
|
1365
|
+
const quoteText = data.quoteText?.trim() || null;
|
|
1366
|
+
const url = data.url?.trim() || null;
|
|
1367
|
+
|
|
1102
1368
|
assertPostFormatShape({
|
|
1103
1369
|
format,
|
|
1104
|
-
title
|
|
1105
|
-
|
|
1106
|
-
|
|
1370
|
+
title,
|
|
1371
|
+
body,
|
|
1372
|
+
url,
|
|
1373
|
+
quoteText,
|
|
1374
|
+
hasAttachments: data.attachments
|
|
1375
|
+
? data.attachments.length > 0
|
|
1376
|
+
: undefined,
|
|
1107
1377
|
});
|
|
1108
1378
|
|
|
1109
|
-
const body = data.bodyMarkdown
|
|
1110
|
-
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1111
|
-
: (data.body ?? null);
|
|
1112
1379
|
const bodyHtml = body ? renderTiptapJson(body) : null;
|
|
1113
1380
|
const bodyText = body ? extractBodyText(body) : null;
|
|
1114
1381
|
|
|
1115
1382
|
// Generate summary for titled notes with body content
|
|
1116
1383
|
let summary: string | null = null;
|
|
1117
|
-
if (format === "note" &&
|
|
1384
|
+
if (format === "note" && title && body && summaryConfig) {
|
|
1118
1385
|
summary = extractSummary(
|
|
1119
1386
|
body,
|
|
1120
1387
|
summaryConfig.maxParagraphs,
|
|
@@ -1127,18 +1394,45 @@ export function createPostService(
|
|
|
1127
1394
|
let status: Status = requestedStatus ?? "published";
|
|
1128
1395
|
let visibility: Visibility | null = requestedVisibility ?? "public";
|
|
1129
1396
|
|
|
1397
|
+
// Collapse the two-input (`flag` + `flagAt`) pair the DTO exposes
|
|
1398
|
+
// into the single timestamp form the DB column stores. Explicit
|
|
1399
|
+
// `*At` wins over the boolean shorthand when both are set.
|
|
1400
|
+
const resolvedPinnedAt =
|
|
1401
|
+
data.pinnedAt !== undefined
|
|
1402
|
+
? data.pinnedAt
|
|
1403
|
+
: data.pinned !== undefined
|
|
1404
|
+
? data.pinned
|
|
1405
|
+
? timestamp
|
|
1406
|
+
: null
|
|
1407
|
+
: null;
|
|
1408
|
+
const resolvedFeaturedAt =
|
|
1409
|
+
data.featuredAt !== undefined
|
|
1410
|
+
? data.featuredAt
|
|
1411
|
+
: data.featured !== undefined
|
|
1412
|
+
? data.featured
|
|
1413
|
+
? timestamp
|
|
1414
|
+
: null
|
|
1415
|
+
: null;
|
|
1416
|
+
|
|
1130
1417
|
if (data.replyToId) {
|
|
1131
1418
|
const parent = await this.getById(data.replyToId);
|
|
1132
1419
|
if (!parent) {
|
|
1133
1420
|
throw new NotFoundError("Parent post");
|
|
1134
1421
|
}
|
|
1135
1422
|
|
|
1136
|
-
if (
|
|
1423
|
+
if (resolvedPinnedAt !== null) {
|
|
1137
1424
|
throw new ConflictError(
|
|
1138
1425
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1139
1426
|
);
|
|
1140
1427
|
}
|
|
1141
1428
|
|
|
1429
|
+
const lastLivePostId = await getLastLivePostIdInThread(parent.threadId);
|
|
1430
|
+
if (lastLivePostId && lastLivePostId !== parent.id) {
|
|
1431
|
+
throw new ConflictError(
|
|
1432
|
+
"This post is no longer the end of the thread. Reply to the latest post instead.",
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1142
1436
|
threadId = parent.threadId;
|
|
1143
1437
|
|
|
1144
1438
|
// Replies inherit visibility from the root at read time.
|
|
@@ -1162,6 +1456,14 @@ export function createPostService(
|
|
|
1162
1456
|
let slug: string;
|
|
1163
1457
|
let aliasPath: string | null = null;
|
|
1164
1458
|
|
|
1459
|
+
// Quote `title` stores source attribution (e.g. author name), not a real
|
|
1460
|
+
// title — deriving the slug from it would produce URLs like `/basho`
|
|
1461
|
+
// that read as "a page about Basho" and collide across every quote from
|
|
1462
|
+
// the same source. Fall through to random IDs instead; users who want a
|
|
1463
|
+
// readable slug can still set one explicitly.
|
|
1464
|
+
const titleForSlug =
|
|
1465
|
+
format === "quote" ? undefined : (title ?? undefined);
|
|
1466
|
+
|
|
1165
1467
|
if (data.path) {
|
|
1166
1468
|
const normalized = normalizePath(data.path);
|
|
1167
1469
|
if (isValidSlug(normalized)) {
|
|
@@ -1176,7 +1478,7 @@ export function createPostService(
|
|
|
1176
1478
|
const slugified = slugify(normalized);
|
|
1177
1479
|
slug = await generatePostSlug({
|
|
1178
1480
|
slug: slugified || undefined,
|
|
1179
|
-
title:
|
|
1481
|
+
title: titleForSlug,
|
|
1180
1482
|
idLength: config.slugIdLength,
|
|
1181
1483
|
isAvailable: isSlugAvailable,
|
|
1182
1484
|
});
|
|
@@ -1189,13 +1491,61 @@ export function createPostService(
|
|
|
1189
1491
|
} else {
|
|
1190
1492
|
slug = await generatePostSlug({
|
|
1191
1493
|
slug: data.slug,
|
|
1192
|
-
title:
|
|
1494
|
+
title: titleForSlug,
|
|
1193
1495
|
idLength: config.slugIdLength,
|
|
1194
1496
|
isAvailable: isSlugAvailable,
|
|
1195
1497
|
});
|
|
1196
1498
|
}
|
|
1197
1499
|
|
|
1198
|
-
|
|
1500
|
+
// When structured `collectionEntries` are provided (Hugo import path),
|
|
1501
|
+
// they win over the bare `collectionIds` slug list and carry
|
|
1502
|
+
// `createdAt` / `position` / `pinnedAt` per row. Otherwise fall back to
|
|
1503
|
+
// the simple list and derive those fields from sensible defaults.
|
|
1504
|
+
const hasCollectionEntries =
|
|
1505
|
+
data.collectionEntries !== undefined &&
|
|
1506
|
+
data.collectionEntries.length > 0;
|
|
1507
|
+
const collectionInsertRows: {
|
|
1508
|
+
siteId: string;
|
|
1509
|
+
postId: string;
|
|
1510
|
+
collectionId: string;
|
|
1511
|
+
createdAt: number;
|
|
1512
|
+
position: number;
|
|
1513
|
+
pinnedAt: number | null;
|
|
1514
|
+
}[] = hasCollectionEntries
|
|
1515
|
+
? (() => {
|
|
1516
|
+
const seen = new Set<string>();
|
|
1517
|
+
const rows: {
|
|
1518
|
+
siteId: string;
|
|
1519
|
+
postId: string;
|
|
1520
|
+
collectionId: string;
|
|
1521
|
+
createdAt: number;
|
|
1522
|
+
position: number;
|
|
1523
|
+
pinnedAt: number | null;
|
|
1524
|
+
}[] = [];
|
|
1525
|
+
let fallbackPosition = 0;
|
|
1526
|
+
for (const entry of data.collectionEntries ?? []) {
|
|
1527
|
+
if (seen.has(entry.collectionId)) continue;
|
|
1528
|
+
seen.add(entry.collectionId);
|
|
1529
|
+
rows.push({
|
|
1530
|
+
siteId,
|
|
1531
|
+
postId: id,
|
|
1532
|
+
collectionId: entry.collectionId,
|
|
1533
|
+
createdAt: entry.createdAt ?? timestamp,
|
|
1534
|
+
position: entry.position ?? fallbackPosition,
|
|
1535
|
+
pinnedAt: entry.pinnedAt ?? null,
|
|
1536
|
+
});
|
|
1537
|
+
fallbackPosition++;
|
|
1538
|
+
}
|
|
1539
|
+
return rows;
|
|
1540
|
+
})()
|
|
1541
|
+
: [...new Set(data.collectionIds ?? [])].map((collectionId, index) => ({
|
|
1542
|
+
siteId,
|
|
1543
|
+
postId: id,
|
|
1544
|
+
collectionId,
|
|
1545
|
+
createdAt: timestamp,
|
|
1546
|
+
position: index,
|
|
1547
|
+
pinnedAt: null,
|
|
1548
|
+
}));
|
|
1199
1549
|
|
|
1200
1550
|
try {
|
|
1201
1551
|
if (usesBatchWrites) {
|
|
@@ -1208,14 +1558,14 @@ export function createPostService(
|
|
|
1208
1558
|
format,
|
|
1209
1559
|
status,
|
|
1210
1560
|
visibility,
|
|
1211
|
-
pinnedAt:
|
|
1212
|
-
featuredAt:
|
|
1213
|
-
title
|
|
1214
|
-
url
|
|
1215
|
-
body
|
|
1561
|
+
pinnedAt: resolvedPinnedAt,
|
|
1562
|
+
featuredAt: resolvedFeaturedAt,
|
|
1563
|
+
title,
|
|
1564
|
+
url,
|
|
1565
|
+
body,
|
|
1216
1566
|
bodyHtml,
|
|
1217
1567
|
bodyText,
|
|
1218
|
-
quoteText
|
|
1568
|
+
quoteText,
|
|
1219
1569
|
summary,
|
|
1220
1570
|
rating,
|
|
1221
1571
|
replyToId: data.replyToId ?? null,
|
|
@@ -1259,16 +1609,9 @@ export function createPostService(
|
|
|
1259
1609
|
);
|
|
1260
1610
|
}
|
|
1261
1611
|
|
|
1262
|
-
if (
|
|
1612
|
+
if (collectionInsertRows.length > 0) {
|
|
1263
1613
|
writeQueries.push(
|
|
1264
|
-
db.insert(postCollections).values(
|
|
1265
|
-
collectionIds.map((collectionId) => ({
|
|
1266
|
-
siteId,
|
|
1267
|
-
postId: id,
|
|
1268
|
-
collectionId,
|
|
1269
|
-
createdAt: timestamp,
|
|
1270
|
-
})),
|
|
1271
|
-
),
|
|
1614
|
+
db.insert(postCollections).values(collectionInsertRows),
|
|
1272
1615
|
);
|
|
1273
1616
|
}
|
|
1274
1617
|
|
|
@@ -1286,14 +1629,14 @@ export function createPostService(
|
|
|
1286
1629
|
format,
|
|
1287
1630
|
status,
|
|
1288
1631
|
visibility,
|
|
1289
|
-
pinnedAt:
|
|
1290
|
-
featuredAt:
|
|
1291
|
-
title
|
|
1292
|
-
url
|
|
1293
|
-
body
|
|
1632
|
+
pinnedAt: resolvedPinnedAt,
|
|
1633
|
+
featuredAt: resolvedFeaturedAt,
|
|
1634
|
+
title,
|
|
1635
|
+
url,
|
|
1636
|
+
body,
|
|
1294
1637
|
bodyHtml,
|
|
1295
1638
|
bodyText,
|
|
1296
|
-
quoteText
|
|
1639
|
+
quoteText,
|
|
1297
1640
|
summary,
|
|
1298
1641
|
rating,
|
|
1299
1642
|
replyToId: data.replyToId ?? null,
|
|
@@ -1332,15 +1675,8 @@ export function createPostService(
|
|
|
1332
1675
|
});
|
|
1333
1676
|
}
|
|
1334
1677
|
|
|
1335
|
-
if (
|
|
1336
|
-
await tx.insert(postCollections).values(
|
|
1337
|
-
collectionIds.map((collectionId) => ({
|
|
1338
|
-
siteId,
|
|
1339
|
-
postId: id,
|
|
1340
|
-
collectionId,
|
|
1341
|
-
createdAt: timestamp,
|
|
1342
|
-
})),
|
|
1343
|
-
);
|
|
1678
|
+
if (collectionInsertRows.length > 0) {
|
|
1679
|
+
await tx.insert(postCollections).values(collectionInsertRows);
|
|
1344
1680
|
}
|
|
1345
1681
|
});
|
|
1346
1682
|
}
|
|
@@ -1360,7 +1696,7 @@ export function createPostService(
|
|
|
1360
1696
|
}
|
|
1361
1697
|
|
|
1362
1698
|
// Bump thread root's lastActivityAt when creating a published reply
|
|
1363
|
-
if (data.replyToId && status === "published") {
|
|
1699
|
+
if (data.replyToId && status === "published" && !data.quietReply) {
|
|
1364
1700
|
await recalculateThreadLastActivity(threadId);
|
|
1365
1701
|
}
|
|
1366
1702
|
|
|
@@ -1373,13 +1709,25 @@ export function createPostService(
|
|
|
1373
1709
|
await createAttachmentMediaIds(attachmentInputs, deps);
|
|
1374
1710
|
|
|
1375
1711
|
try {
|
|
1376
|
-
const post = await this.create(
|
|
1712
|
+
const post = await this.create(
|
|
1713
|
+
{ ...data, attachments: attachmentInputs },
|
|
1714
|
+
summaryConfig,
|
|
1715
|
+
);
|
|
1377
1716
|
|
|
1378
1717
|
try {
|
|
1379
1718
|
if (orderedMediaIds.length > 0) {
|
|
1380
1719
|
await deps.media.attachToPost(post.id, orderedMediaIds);
|
|
1381
1720
|
}
|
|
1382
1721
|
await applyAttachmentAltUpdates(attachmentInputs, deps);
|
|
1722
|
+
|
|
1723
|
+
// Best-effort: fetch and store link preview thumbnail
|
|
1724
|
+
await resolveAndStorePreview(
|
|
1725
|
+
post.id,
|
|
1726
|
+
post.format,
|
|
1727
|
+
post.url,
|
|
1728
|
+
deps.storage,
|
|
1729
|
+
);
|
|
1730
|
+
|
|
1383
1731
|
return post;
|
|
1384
1732
|
} catch (error) {
|
|
1385
1733
|
await deps.media.attachToPost(post.id, []).catch(() => undefined);
|
|
@@ -1396,6 +1744,47 @@ export function createPostService(
|
|
|
1396
1744
|
}
|
|
1397
1745
|
},
|
|
1398
1746
|
|
|
1747
|
+
async createThreadWithAttachments(items, deps, summaryConfig) {
|
|
1748
|
+
if (items.length < 2) {
|
|
1749
|
+
throw new ValidationError("A thread requires at least 2 posts.");
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const created: Post[] = [];
|
|
1753
|
+
|
|
1754
|
+
for (let i = 0; i < items.length; i++) {
|
|
1755
|
+
const item = items[i];
|
|
1756
|
+
if (!item) continue;
|
|
1757
|
+
const { data, attachments } = item;
|
|
1758
|
+
const prevPost = created[i - 1];
|
|
1759
|
+
const postData: CreatePost = {
|
|
1760
|
+
...data,
|
|
1761
|
+
// Chain each post as a reply to the previous one (server-side chaining)
|
|
1762
|
+
replyToId: i === 0 ? data.replyToId : prevPost?.id,
|
|
1763
|
+
};
|
|
1764
|
+
|
|
1765
|
+
try {
|
|
1766
|
+
const post = await this.createWithAttachments(
|
|
1767
|
+
postData,
|
|
1768
|
+
attachments,
|
|
1769
|
+
deps,
|
|
1770
|
+
summaryConfig,
|
|
1771
|
+
);
|
|
1772
|
+
created.push(post);
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
// Rollback: delete all already-created posts in reverse order
|
|
1775
|
+
for (const p of [...created].reverse()) {
|
|
1776
|
+
await this.delete(p.id, {
|
|
1777
|
+
media: deps.media,
|
|
1778
|
+
storage: deps.storage,
|
|
1779
|
+
}).catch(() => undefined);
|
|
1780
|
+
}
|
|
1781
|
+
throw error;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
return created;
|
|
1786
|
+
},
|
|
1787
|
+
|
|
1399
1788
|
async update(id, data, summaryConfig) {
|
|
1400
1789
|
const existing = await this.getById(id);
|
|
1401
1790
|
if (!existing) return null;
|
|
@@ -1405,9 +1794,12 @@ export function createPostService(
|
|
|
1405
1794
|
data.format !== undefined
|
|
1406
1795
|
? ensurePostFormat(data.format)
|
|
1407
1796
|
: existing.format;
|
|
1408
|
-
const nextUrl =
|
|
1797
|
+
const nextUrl =
|
|
1798
|
+
data.url !== undefined ? data.url?.trim() || null : existing.url;
|
|
1409
1799
|
const nextQuoteText =
|
|
1410
|
-
data.quoteText !== undefined
|
|
1800
|
+
data.quoteText !== undefined
|
|
1801
|
+
? data.quoteText?.trim() || null
|
|
1802
|
+
: existing.quoteText;
|
|
1411
1803
|
const nextStatus =
|
|
1412
1804
|
data.status !== undefined
|
|
1413
1805
|
? ensurePostStatus(data.status)
|
|
@@ -1419,11 +1811,26 @@ export function createPostService(
|
|
|
1419
1811
|
const nextRating =
|
|
1420
1812
|
data.rating !== undefined ? ensurePostRating(data.rating) : undefined;
|
|
1421
1813
|
|
|
1814
|
+
const nextTitle =
|
|
1815
|
+
data.title !== undefined ? data.title?.trim() || null : existing.title;
|
|
1816
|
+
|
|
1422
1817
|
assertPostFormatShape({
|
|
1423
1818
|
format: nextFormat,
|
|
1424
|
-
title:
|
|
1819
|
+
title: nextTitle,
|
|
1820
|
+
body:
|
|
1821
|
+
data.body !== undefined || data.bodyMarkdown !== undefined
|
|
1822
|
+
? data.bodyMarkdown
|
|
1823
|
+
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1824
|
+
: (data.body ?? null)
|
|
1825
|
+
: existing.body,
|
|
1425
1826
|
url: nextUrl,
|
|
1426
1827
|
quoteText: nextQuoteText,
|
|
1828
|
+
// During update we can't cheaply resolve final attachment state;
|
|
1829
|
+
// existing posts already passed creation validation, so we only
|
|
1830
|
+
// guard against clearing all text fields here.
|
|
1831
|
+
hasAttachments: data.attachments
|
|
1832
|
+
? data.attachments.length > 0
|
|
1833
|
+
: undefined,
|
|
1427
1834
|
});
|
|
1428
1835
|
assertDraftPublishedAt(nextStatus, data.publishedAt);
|
|
1429
1836
|
|
|
@@ -1446,19 +1853,27 @@ export function createPostService(
|
|
|
1446
1853
|
}
|
|
1447
1854
|
|
|
1448
1855
|
if (data.format !== undefined) updates.format = nextFormat;
|
|
1449
|
-
if (data.title !== undefined) updates.title =
|
|
1450
|
-
if (data.url !== undefined) updates.url =
|
|
1451
|
-
if (data.quoteText !== undefined) updates.quoteText =
|
|
1856
|
+
if (data.title !== undefined) updates.title = nextTitle;
|
|
1857
|
+
if (data.url !== undefined) updates.url = nextUrl;
|
|
1858
|
+
if (data.quoteText !== undefined) updates.quoteText = nextQuoteText;
|
|
1452
1859
|
if (data.rating !== undefined) updates.rating = nextRating;
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1860
|
+
// Prefer explicit timestamp when provided; fall back to boolean shorthand.
|
|
1861
|
+
if (data.pinnedAt !== undefined) {
|
|
1862
|
+
updates.pinnedAt = data.pinnedAt;
|
|
1863
|
+
} else if (data.pinned !== undefined) {
|
|
1864
|
+
updates.pinnedAt = data.pinned ? timestamp : null;
|
|
1865
|
+
}
|
|
1866
|
+
if (data.featuredAt !== undefined) {
|
|
1867
|
+
updates.featuredAt = data.featuredAt;
|
|
1868
|
+
} else if (data.featured !== undefined) {
|
|
1869
|
+
updates.featuredAt = data.featured ? timestamp : null;
|
|
1870
|
+
}
|
|
1457
1871
|
|
|
1458
1872
|
if (data.body !== undefined || data.bodyMarkdown !== undefined) {
|
|
1459
|
-
const
|
|
1873
|
+
const rawBody = data.bodyMarkdown
|
|
1460
1874
|
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1461
1875
|
: (data.body ?? null);
|
|
1876
|
+
const normalizedBody = rawBody ? trimTiptapBody(rawBody) : null;
|
|
1462
1877
|
updates.body = normalizedBody;
|
|
1463
1878
|
updates.bodyHtml = normalizedBody
|
|
1464
1879
|
? renderTiptapJson(normalizedBody)
|
|
@@ -1471,7 +1886,7 @@ export function createPostService(
|
|
|
1471
1886
|
// Recompute summary when body, title, or format change
|
|
1472
1887
|
if (summaryConfig) {
|
|
1473
1888
|
const format = nextFormat;
|
|
1474
|
-
const title =
|
|
1889
|
+
const title = nextTitle;
|
|
1475
1890
|
const body =
|
|
1476
1891
|
data.bodyMarkdown !== undefined
|
|
1477
1892
|
? data.bodyMarkdown
|
|
@@ -1498,7 +1913,10 @@ export function createPostService(
|
|
|
1498
1913
|
"Cannot change visibility of a thread reply. Update the root post instead.",
|
|
1499
1914
|
);
|
|
1500
1915
|
}
|
|
1501
|
-
if (
|
|
1916
|
+
if (
|
|
1917
|
+
(data.pinnedAt !== undefined && data.pinnedAt !== null) ||
|
|
1918
|
+
data.pinned === true
|
|
1919
|
+
) {
|
|
1502
1920
|
throw new ConflictError(
|
|
1503
1921
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1504
1922
|
);
|
|
@@ -1557,9 +1975,9 @@ export function createPostService(
|
|
|
1557
1975
|
}
|
|
1558
1976
|
|
|
1559
1977
|
// Complex case: cascade + update + collection sync atomically
|
|
1560
|
-
const
|
|
1561
|
-
? await
|
|
1562
|
-
:
|
|
1978
|
+
const existingCollectionTimestamps = needsCollectionSync
|
|
1979
|
+
? await getPostCollectionTimestamps(id)
|
|
1980
|
+
: new Map<string, number>();
|
|
1563
1981
|
let updateResult: (typeof posts.$inferSelect)[] | undefined;
|
|
1564
1982
|
|
|
1565
1983
|
if (usesBatchWrites) {
|
|
@@ -1614,38 +2032,30 @@ export function createPostService(
|
|
|
1614
2032
|
);
|
|
1615
2033
|
|
|
1616
2034
|
if (needsCollectionSync) {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (removedIds.length > 0) {
|
|
1627
|
-
writeQueries.push(
|
|
1628
|
-
db
|
|
1629
|
-
.delete(postCollections)
|
|
1630
|
-
.where(
|
|
1631
|
-
and(
|
|
1632
|
-
eq(postCollections.siteId, siteId),
|
|
1633
|
-
eq(postCollections.postId, id),
|
|
1634
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1635
|
-
),
|
|
2035
|
+
// Delete all and re-insert to preserve user-specified ordering
|
|
2036
|
+
writeQueries.push(
|
|
2037
|
+
db
|
|
2038
|
+
.delete(postCollections)
|
|
2039
|
+
.where(
|
|
2040
|
+
and(
|
|
2041
|
+
eq(postCollections.siteId, siteId),
|
|
2042
|
+
eq(postCollections.postId, id),
|
|
1636
2043
|
),
|
|
1637
|
-
|
|
1638
|
-
|
|
2044
|
+
),
|
|
2045
|
+
);
|
|
1639
2046
|
|
|
1640
|
-
if (
|
|
2047
|
+
if (nextCollectionIds.length > 0) {
|
|
1641
2048
|
const collectionTimestamp = now();
|
|
1642
2049
|
writeQueries.push(
|
|
1643
2050
|
db.insert(postCollections).values(
|
|
1644
|
-
|
|
2051
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1645
2052
|
siteId,
|
|
1646
2053
|
postId: id,
|
|
1647
2054
|
collectionId,
|
|
1648
|
-
createdAt:
|
|
2055
|
+
createdAt:
|
|
2056
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2057
|
+
collectionTimestamp,
|
|
2058
|
+
position: index,
|
|
1649
2059
|
})),
|
|
1650
2060
|
),
|
|
1651
2061
|
);
|
|
@@ -1705,35 +2115,27 @@ export function createPostService(
|
|
|
1705
2115
|
.returning();
|
|
1706
2116
|
|
|
1707
2117
|
if (needsCollectionSync) {
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (removedIds.length > 0) {
|
|
1718
|
-
await tx
|
|
1719
|
-
.delete(postCollections)
|
|
1720
|
-
.where(
|
|
1721
|
-
and(
|
|
1722
|
-
eq(postCollections.siteId, siteId),
|
|
1723
|
-
eq(postCollections.postId, id),
|
|
1724
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1725
|
-
),
|
|
1726
|
-
);
|
|
1727
|
-
}
|
|
2118
|
+
// Delete all and re-insert to preserve user-specified ordering
|
|
2119
|
+
await tx
|
|
2120
|
+
.delete(postCollections)
|
|
2121
|
+
.where(
|
|
2122
|
+
and(
|
|
2123
|
+
eq(postCollections.siteId, siteId),
|
|
2124
|
+
eq(postCollections.postId, id),
|
|
2125
|
+
),
|
|
2126
|
+
);
|
|
1728
2127
|
|
|
1729
|
-
if (
|
|
2128
|
+
if (nextCollectionIds.length > 0) {
|
|
1730
2129
|
const collectionTimestamp = now();
|
|
1731
2130
|
await tx.insert(postCollections).values(
|
|
1732
|
-
|
|
2131
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1733
2132
|
siteId,
|
|
1734
2133
|
postId: id,
|
|
1735
2134
|
collectionId,
|
|
1736
|
-
createdAt:
|
|
2135
|
+
createdAt:
|
|
2136
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2137
|
+
collectionTimestamp,
|
|
2138
|
+
position: index,
|
|
1737
2139
|
})),
|
|
1738
2140
|
);
|
|
1739
2141
|
}
|
|
@@ -1750,7 +2152,32 @@ export function createPostService(
|
|
|
1750
2152
|
|
|
1751
2153
|
async updateWithAttachments(id, data, attachments, deps, summaryConfig) {
|
|
1752
2154
|
if (attachments === undefined) {
|
|
1753
|
-
|
|
2155
|
+
const existing = await this.getById(id);
|
|
2156
|
+
const post = await this.update(id, data, summaryConfig);
|
|
2157
|
+
if (post && existing) {
|
|
2158
|
+
const urlChanged =
|
|
2159
|
+
data.url !== undefined && data.url !== existing.url;
|
|
2160
|
+
const formatChanged =
|
|
2161
|
+
data.format !== undefined && data.format !== existing.format;
|
|
2162
|
+
if (urlChanged || formatChanged) {
|
|
2163
|
+
await deletePreviewImage(existing.previewImageKey, deps.storage);
|
|
2164
|
+
await db
|
|
2165
|
+
.update(posts)
|
|
2166
|
+
.set({
|
|
2167
|
+
previewImageKey: null,
|
|
2168
|
+
previewKind: null,
|
|
2169
|
+
previewProvider: null,
|
|
2170
|
+
})
|
|
2171
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
|
|
2172
|
+
await resolveAndStorePreview(
|
|
2173
|
+
post.id,
|
|
2174
|
+
post.format,
|
|
2175
|
+
post.url,
|
|
2176
|
+
deps.storage,
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return post;
|
|
1754
2181
|
}
|
|
1755
2182
|
|
|
1756
2183
|
const existingPost = await this.getById(id);
|
|
@@ -1788,17 +2215,41 @@ export function createPostService(
|
|
|
1788
2215
|
await applyAttachmentAltUpdates(attachments, deps);
|
|
1789
2216
|
|
|
1790
2217
|
const nextAttachmentIds = new Set(orderedMediaIds);
|
|
1791
|
-
const
|
|
1792
|
-
.filter(
|
|
1793
|
-
(attachment) =>
|
|
1794
|
-
attachment.mimeType === "text/x-tiptap+json" &&
|
|
1795
|
-
!nextAttachmentIds.has(attachment.id),
|
|
1796
|
-
)
|
|
2218
|
+
const removedAttachmentIds = existingAttachments
|
|
2219
|
+
.filter((attachment) => !nextAttachmentIds.has(attachment.id))
|
|
1797
2220
|
.map((attachment) => attachment.id);
|
|
1798
2221
|
await deps.media
|
|
1799
|
-
.deleteByIds(
|
|
2222
|
+
.deleteByIds(removedAttachmentIds, deps.storage)
|
|
1800
2223
|
.catch(() => undefined);
|
|
1801
2224
|
|
|
2225
|
+
// Best-effort: update link preview when URL changes
|
|
2226
|
+
const urlChanged =
|
|
2227
|
+
data.url !== undefined && data.url !== existingPost.url;
|
|
2228
|
+
const formatChanged =
|
|
2229
|
+
data.format !== undefined && data.format !== existingPost.format;
|
|
2230
|
+
if (urlChanged || formatChanged) {
|
|
2231
|
+
// Clean up old preview image
|
|
2232
|
+
await deletePreviewImage(existingPost.previewImageKey, deps.storage);
|
|
2233
|
+
|
|
2234
|
+
// Clear preview fields first (may be re-set by resolveAndStorePreview)
|
|
2235
|
+
await db
|
|
2236
|
+
.update(posts)
|
|
2237
|
+
.set({
|
|
2238
|
+
previewImageKey: null,
|
|
2239
|
+
previewKind: null,
|
|
2240
|
+
previewProvider: null,
|
|
2241
|
+
})
|
|
2242
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, post.id)));
|
|
2243
|
+
|
|
2244
|
+
// Fetch new preview if applicable
|
|
2245
|
+
await resolveAndStorePreview(
|
|
2246
|
+
post.id,
|
|
2247
|
+
post.format,
|
|
2248
|
+
post.url,
|
|
2249
|
+
deps.storage,
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
1802
2253
|
return post;
|
|
1803
2254
|
} catch (error) {
|
|
1804
2255
|
if (replacedAttachments) {
|
|
@@ -1826,17 +2277,16 @@ export function createPostService(
|
|
|
1826
2277
|
const existing = await this.getById(id);
|
|
1827
2278
|
if (!existing) return false;
|
|
1828
2279
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
const thread = await this.getThread(id);
|
|
1834
|
-
postIds = thread.map((p) => p.id);
|
|
1835
|
-
} else {
|
|
1836
|
-
postIds = [id];
|
|
1837
|
-
}
|
|
2280
|
+
const isRoot = !isThreadReply(existing);
|
|
2281
|
+
const affectedPosts: Post[] = isRoot
|
|
2282
|
+
? await this.getThread(id)
|
|
2283
|
+
: [existing];
|
|
1838
2284
|
|
|
1839
|
-
|
|
2285
|
+
// Clean up media and preview images
|
|
2286
|
+
if (deps?.media) {
|
|
2287
|
+
const mediaMap = await deps.media.getByPostIds(
|
|
2288
|
+
affectedPosts.map((p) => p.id),
|
|
2289
|
+
);
|
|
1840
2290
|
const allMedia = [...mediaMap.values()].flat();
|
|
1841
2291
|
if (allMedia.length > 0) {
|
|
1842
2292
|
await deps.media.deleteByIds(
|
|
@@ -1844,18 +2294,20 @@ export function createPostService(
|
|
|
1844
2294
|
deps.storage,
|
|
1845
2295
|
);
|
|
1846
2296
|
}
|
|
2297
|
+
|
|
2298
|
+
for (const p of affectedPosts) {
|
|
2299
|
+
await deletePreviewImage(p.previewImageKey, deps.storage);
|
|
2300
|
+
}
|
|
1847
2301
|
}
|
|
1848
2302
|
|
|
1849
2303
|
const timestamp = now();
|
|
1850
2304
|
|
|
1851
|
-
|
|
1852
|
-
if (!isThreadReply(existing)) {
|
|
2305
|
+
if (isRoot) {
|
|
1853
2306
|
await db
|
|
1854
2307
|
.update(posts)
|
|
1855
2308
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
1856
2309
|
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
1857
2310
|
} else {
|
|
1858
|
-
// Soft-delete the single reply
|
|
1859
2311
|
await db
|
|
1860
2312
|
.update(posts)
|
|
1861
2313
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
@@ -1866,6 +2318,23 @@ export function createPostService(
|
|
|
1866
2318
|
return true;
|
|
1867
2319
|
},
|
|
1868
2320
|
|
|
2321
|
+
async deleteThreadDraft(id, deps) {
|
|
2322
|
+
const deleted = await this.delete(id, deps);
|
|
2323
|
+
if (!deleted) return false;
|
|
2324
|
+
|
|
2325
|
+
// Release path_registry entries for all posts in the thread so slugs
|
|
2326
|
+
// can be reused by the replacement thread.
|
|
2327
|
+
const threadRows = await db
|
|
2328
|
+
.select({ id: posts.id })
|
|
2329
|
+
.from(posts)
|
|
2330
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
2331
|
+
for (const row of threadRows) {
|
|
2332
|
+
await resolvedPaths.deleteByPostId(row.id);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
return true;
|
|
2336
|
+
},
|
|
2337
|
+
|
|
1869
2338
|
async getThread(rootId) {
|
|
1870
2339
|
const rows = await db
|
|
1871
2340
|
.select()
|
|
@@ -1955,7 +2424,7 @@ export function createPostService(
|
|
|
1955
2424
|
const rows = await db
|
|
1956
2425
|
.select({
|
|
1957
2426
|
threadId: posts.threadId,
|
|
1958
|
-
count: sql<number>`count(*)`.as("count"),
|
|
2427
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
1959
2428
|
})
|
|
1960
2429
|
.from(posts)
|
|
1961
2430
|
.where(
|
|
@@ -1984,10 +2453,10 @@ export function createPostService(
|
|
|
1984
2453
|
id: posts.id,
|
|
1985
2454
|
threadId: posts.threadId,
|
|
1986
2455
|
createdAt: posts.createdAt,
|
|
1987
|
-
previewRank: sql<number>`ROW_NUMBER() OVER (
|
|
2456
|
+
previewRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
1988
2457
|
PARTITION BY ${posts.threadId}
|
|
1989
2458
|
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
1990
|
-
)`.as("preview_rank"),
|
|
2459
|
+
) AS INTEGER)`.as("preview_rank"),
|
|
1991
2460
|
})
|
|
1992
2461
|
.from(posts)
|
|
1993
2462
|
.where(
|
|
@@ -2041,14 +2510,17 @@ export function createPostService(
|
|
|
2041
2510
|
.select({
|
|
2042
2511
|
id: posts.id,
|
|
2043
2512
|
threadId: posts.threadId,
|
|
2044
|
-
|
|
2045
|
-
|
|
2513
|
+
firstReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2514
|
+
PARTITION BY ${posts.threadId}
|
|
2515
|
+
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
2516
|
+
) AS INTEGER)`.as("first_reply_rank"),
|
|
2517
|
+
latestReplyRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2046
2518
|
PARTITION BY ${posts.threadId}
|
|
2047
2519
|
ORDER BY ${posts.createdAt} DESC, ${posts.id} DESC
|
|
2048
|
-
)`.as("
|
|
2049
|
-
totalReplyCount: sql<number>`COUNT(*) OVER (
|
|
2520
|
+
) AS INTEGER)`.as("latest_reply_rank"),
|
|
2521
|
+
totalReplyCount: sql<number>`CAST(COUNT(*) OVER (
|
|
2050
2522
|
PARTITION BY ${posts.threadId}
|
|
2051
|
-
)`.as("total_reply_count"),
|
|
2523
|
+
) AS INTEGER)`.as("total_reply_count"),
|
|
2052
2524
|
})
|
|
2053
2525
|
.from(posts)
|
|
2054
2526
|
.where(
|
|
@@ -2062,41 +2534,72 @@ export function createPostService(
|
|
|
2062
2534
|
)
|
|
2063
2535
|
.as("ranked_replies");
|
|
2064
2536
|
|
|
2065
|
-
const
|
|
2537
|
+
const contextRows = await db
|
|
2066
2538
|
.select({
|
|
2067
2539
|
threadId: rankedReplies.threadId,
|
|
2068
|
-
|
|
2069
|
-
|
|
2540
|
+
id: rankedReplies.id,
|
|
2541
|
+
firstReplyRank: rankedReplies.firstReplyRank,
|
|
2542
|
+
latestReplyRank: rankedReplies.latestReplyRank,
|
|
2070
2543
|
totalReplyCount: rankedReplies.totalReplyCount,
|
|
2071
2544
|
})
|
|
2072
2545
|
.from(rankedReplies)
|
|
2073
|
-
.where(
|
|
2546
|
+
.where(
|
|
2547
|
+
or(
|
|
2548
|
+
eq(rankedReplies.firstReplyRank, 1),
|
|
2549
|
+
lte(rankedReplies.latestReplyRank, 2),
|
|
2550
|
+
),
|
|
2551
|
+
);
|
|
2074
2552
|
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2553
|
+
const hydratedPosts = await hydratePostsById(
|
|
2554
|
+
contextRows.map((row) => row.id),
|
|
2555
|
+
);
|
|
2077
2556
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2557
|
+
const contextByThreadId = new Map<
|
|
2558
|
+
string,
|
|
2559
|
+
{
|
|
2560
|
+
secondReplyId: string | null;
|
|
2561
|
+
penultimateReplyId: string | null;
|
|
2562
|
+
latestReplyId: string | null;
|
|
2563
|
+
totalReplyCount: number;
|
|
2080
2564
|
}
|
|
2565
|
+
>();
|
|
2566
|
+
for (const row of contextRows) {
|
|
2567
|
+
const existing = contextByThreadId.get(row.threadId) ?? {
|
|
2568
|
+
secondReplyId: null,
|
|
2569
|
+
penultimateReplyId: null,
|
|
2570
|
+
latestReplyId: null,
|
|
2571
|
+
totalReplyCount: row.totalReplyCount,
|
|
2572
|
+
};
|
|
2081
2573
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2574
|
+
if (row.firstReplyRank === 1) {
|
|
2575
|
+
existing.secondReplyId = row.id;
|
|
2576
|
+
}
|
|
2577
|
+
if (row.latestReplyRank === 2) {
|
|
2578
|
+
existing.penultimateReplyId = row.id;
|
|
2579
|
+
}
|
|
2580
|
+
if (row.latestReplyRank === 1) {
|
|
2581
|
+
existing.latestReplyId = row.id;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
contextByThreadId.set(row.threadId, existing);
|
|
2585
|
+
}
|
|
2085
2586
|
|
|
2086
2587
|
const result = new Map<string, ThreadTimelineContext>();
|
|
2087
|
-
for (const
|
|
2088
|
-
|
|
2089
|
-
if (!latestReply) continue;
|
|
2588
|
+
for (const [threadId, context] of contextByThreadId) {
|
|
2589
|
+
if (!context.latestReplyId) continue;
|
|
2090
2590
|
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
? (hydratedPosts.get(row.latestReplyToId) ?? null)
|
|
2094
|
-
: null;
|
|
2591
|
+
const latestReply = hydratedPosts.get(context.latestReplyId);
|
|
2592
|
+
if (!latestReply) continue;
|
|
2095
2593
|
|
|
2096
|
-
result.set(
|
|
2594
|
+
result.set(threadId, {
|
|
2595
|
+
secondReply: context.secondReplyId
|
|
2596
|
+
? (hydratedPosts.get(context.secondReplyId) ?? null)
|
|
2597
|
+
: null,
|
|
2598
|
+
penultimateReply: context.penultimateReplyId
|
|
2599
|
+
? (hydratedPosts.get(context.penultimateReplyId) ?? null)
|
|
2600
|
+
: null,
|
|
2097
2601
|
latestReply,
|
|
2098
|
-
|
|
2099
|
-
totalReplyCount: row.totalReplyCount,
|
|
2602
|
+
totalReplyCount: context.totalReplyCount,
|
|
2100
2603
|
});
|
|
2101
2604
|
}
|
|
2102
2605
|
|
|
@@ -2111,7 +2614,10 @@ export function createPostService(
|
|
|
2111
2614
|
|
|
2112
2615
|
const rows = await db
|
|
2113
2616
|
.select({
|
|
2114
|
-
count:
|
|
2617
|
+
count:
|
|
2618
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2619
|
+
"count",
|
|
2620
|
+
),
|
|
2115
2621
|
})
|
|
2116
2622
|
.from(posts)
|
|
2117
2623
|
.where(and(...conditions));
|
|
@@ -2150,14 +2656,27 @@ export function createPostService(
|
|
|
2150
2656
|
},
|
|
2151
2657
|
|
|
2152
2658
|
async countCollectionThreadRoots(collectionId, options = {}) {
|
|
2659
|
+
return this.countCollectionThreadRootsForCollections(
|
|
2660
|
+
[collectionId],
|
|
2661
|
+
options,
|
|
2662
|
+
);
|
|
2663
|
+
},
|
|
2664
|
+
|
|
2665
|
+
async countCollectionThreadRootsForCollections(
|
|
2666
|
+
collectionIds,
|
|
2667
|
+
options = {},
|
|
2668
|
+
) {
|
|
2153
2669
|
const conditions = [
|
|
2154
2670
|
...buildThreadRootPageConditions(options),
|
|
2155
|
-
|
|
2671
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2156
2672
|
];
|
|
2157
2673
|
|
|
2158
2674
|
const rows = await db
|
|
2159
2675
|
.select({
|
|
2160
|
-
count:
|
|
2676
|
+
count:
|
|
2677
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2678
|
+
"count",
|
|
2679
|
+
),
|
|
2161
2680
|
})
|
|
2162
2681
|
.from(posts)
|
|
2163
2682
|
.innerJoin(
|
|
@@ -2173,15 +2692,25 @@ export function createPostService(
|
|
|
2173
2692
|
},
|
|
2174
2693
|
|
|
2175
2694
|
async listCollectionThreadRootIds(collectionId, options = {}) {
|
|
2695
|
+
return this.listCollectionThreadRootIdsForCollections(
|
|
2696
|
+
[collectionId],
|
|
2697
|
+
options,
|
|
2698
|
+
);
|
|
2699
|
+
},
|
|
2700
|
+
|
|
2701
|
+
async listCollectionThreadRootIdsForCollections(
|
|
2702
|
+
collectionIds,
|
|
2703
|
+
options = {},
|
|
2704
|
+
) {
|
|
2176
2705
|
const conditions = [
|
|
2177
2706
|
...buildThreadRootPageConditions(options),
|
|
2178
|
-
|
|
2707
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2179
2708
|
];
|
|
2180
2709
|
const sortOrder = options.sortOrder ?? "newest";
|
|
2181
|
-
const
|
|
2710
|
+
const publishedAt =
|
|
2182
2711
|
sortOrder === "oldest"
|
|
2183
|
-
? sql<number>`MIN(${
|
|
2184
|
-
: sql<number>`MAX(${
|
|
2712
|
+
? sql<number>`MIN(${posts.publishedAt})`.as("published_at")
|
|
2713
|
+
: sql<number>`MAX(${posts.publishedAt})`.as("published_at");
|
|
2185
2714
|
const ratingPresence = sql<number>`MAX(
|
|
2186
2715
|
CASE
|
|
2187
2716
|
WHEN ${posts.rating} IS NULL THEN 0
|
|
@@ -2192,10 +2721,16 @@ export function createPostService(
|
|
|
2192
2721
|
"rating_value",
|
|
2193
2722
|
);
|
|
2194
2723
|
|
|
2724
|
+
const collectionPinnedAt =
|
|
2725
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2726
|
+
"collection_pinned_at",
|
|
2727
|
+
);
|
|
2728
|
+
|
|
2195
2729
|
const baseQuery = db
|
|
2196
2730
|
.select({
|
|
2197
2731
|
threadId: posts.threadId,
|
|
2198
|
-
|
|
2732
|
+
publishedAt,
|
|
2733
|
+
collectionPinnedAt,
|
|
2199
2734
|
ratingPresence,
|
|
2200
2735
|
ratingValue,
|
|
2201
2736
|
})
|
|
@@ -2212,15 +2747,24 @@ export function createPostService(
|
|
|
2212
2747
|
|
|
2213
2748
|
let query =
|
|
2214
2749
|
sortOrder === "oldest"
|
|
2215
|
-
? baseQuery.orderBy(
|
|
2750
|
+
? baseQuery.orderBy(
|
|
2751
|
+
desc(collectionPinnedAt),
|
|
2752
|
+
asc(publishedAt),
|
|
2753
|
+
asc(posts.threadId),
|
|
2754
|
+
)
|
|
2216
2755
|
: sortOrder === "rating_desc"
|
|
2217
2756
|
? baseQuery.orderBy(
|
|
2757
|
+
desc(collectionPinnedAt),
|
|
2218
2758
|
desc(ratingPresence),
|
|
2219
2759
|
desc(ratingValue),
|
|
2220
|
-
desc(
|
|
2760
|
+
desc(publishedAt),
|
|
2221
2761
|
desc(posts.threadId),
|
|
2222
2762
|
)
|
|
2223
|
-
: baseQuery.orderBy(
|
|
2763
|
+
: baseQuery.orderBy(
|
|
2764
|
+
desc(collectionPinnedAt),
|
|
2765
|
+
desc(publishedAt),
|
|
2766
|
+
desc(posts.threadId),
|
|
2767
|
+
);
|
|
2224
2768
|
|
|
2225
2769
|
if (options.limit !== undefined) {
|
|
2226
2770
|
query = query.limit(options.limit) as typeof query;
|
|
@@ -2234,18 +2778,34 @@ export function createPostService(
|
|
|
2234
2778
|
},
|
|
2235
2779
|
|
|
2236
2780
|
async listCollectionFeedEntries(collectionId, options = {}) {
|
|
2781
|
+
return this.listCollectionFeedEntriesForCollections(
|
|
2782
|
+
[collectionId],
|
|
2783
|
+
options,
|
|
2784
|
+
);
|
|
2785
|
+
},
|
|
2786
|
+
|
|
2787
|
+
async listCollectionFeedEntriesForCollections(collectionIds, options = {}) {
|
|
2237
2788
|
const conditions = [
|
|
2238
2789
|
...buildThreadRootPageConditions(options),
|
|
2239
|
-
|
|
2790
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2240
2791
|
];
|
|
2792
|
+
const publishedAt = sql<number>`MAX(${posts.publishedAt})`.as(
|
|
2793
|
+
"published_at",
|
|
2794
|
+
);
|
|
2241
2795
|
const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
|
|
2242
2796
|
"collected_at",
|
|
2243
2797
|
);
|
|
2798
|
+
const collectionPinnedAt =
|
|
2799
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2800
|
+
"collection_pinned_at",
|
|
2801
|
+
);
|
|
2244
2802
|
|
|
2245
2803
|
let query = db
|
|
2246
2804
|
.select({
|
|
2247
2805
|
threadId: posts.threadId,
|
|
2806
|
+
publishedAt,
|
|
2248
2807
|
collectedAt,
|
|
2808
|
+
collectionPinnedAt,
|
|
2249
2809
|
})
|
|
2250
2810
|
.from(posts)
|
|
2251
2811
|
.innerJoin(
|
|
@@ -2257,7 +2817,11 @@ export function createPostService(
|
|
|
2257
2817
|
)
|
|
2258
2818
|
.where(and(...conditions))
|
|
2259
2819
|
.groupBy(posts.threadId)
|
|
2260
|
-
.orderBy(
|
|
2820
|
+
.orderBy(
|
|
2821
|
+
desc(collectionPinnedAt),
|
|
2822
|
+
desc(publishedAt),
|
|
2823
|
+
desc(posts.threadId),
|
|
2824
|
+
);
|
|
2261
2825
|
|
|
2262
2826
|
if (options.limit !== undefined) {
|
|
2263
2827
|
query = query.limit(options.limit) as typeof query;
|
|
@@ -2306,6 +2870,13 @@ export function createPostService(
|
|
|
2306
2870
|
},
|
|
2307
2871
|
|
|
2308
2872
|
async getCollectionPostIdsByThread(collectionId, threadIds) {
|
|
2873
|
+
return this.getCollectionPostIdsByThreadForCollections(
|
|
2874
|
+
[collectionId],
|
|
2875
|
+
threadIds,
|
|
2876
|
+
);
|
|
2877
|
+
},
|
|
2878
|
+
|
|
2879
|
+
async getCollectionPostIdsByThreadForCollections(collectionIds, threadIds) {
|
|
2309
2880
|
const result = new Map<string, string[]>();
|
|
2310
2881
|
if (threadIds.length === 0) return result;
|
|
2311
2882
|
|
|
@@ -2327,12 +2898,13 @@ export function createPostService(
|
|
|
2327
2898
|
.where(
|
|
2328
2899
|
and(
|
|
2329
2900
|
eq(posts.siteId, siteId),
|
|
2330
|
-
|
|
2901
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2331
2902
|
inArray(posts.threadId, chunk),
|
|
2332
2903
|
eq(posts.status, "published"),
|
|
2333
2904
|
isNull(posts.deletedAt),
|
|
2334
2905
|
),
|
|
2335
2906
|
)
|
|
2907
|
+
.groupBy(posts.threadId, posts.id)
|
|
2336
2908
|
.orderBy(posts.threadId, posts.createdAt, posts.id),
|
|
2337
2909
|
);
|
|
2338
2910
|
|