@jant/core 0.3.40 → 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 -34
- package/bin/commands/assets/upload.js +247 -0
- package/bin/commands/collections.js +268 -0
- package/bin/commands/import-site.js +1169 -343
- package/bin/commands/media.js +302 -0
- package/bin/commands/migrate-text-attachments.js +183 -0
- package/bin/commands/migrate.js +263 -2
- package/bin/commands/posts.js +262 -0
- package/bin/commands/search.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-CAtsuLLh.js → app-CwCepTko.js} +23749 -23059
- 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 +60 -13
- package/src/client/__tests__/collection-form-bridge.test.ts +87 -7
- package/src/client/__tests__/collection-page-actions.test.ts +4 -4
- package/src/client/__tests__/collection-picker-order.test.ts +53 -0
- package/src/client/__tests__/collection-sort-menu.test.ts +2 -2
- package/src/client/__tests__/compose-bridge.test.ts +305 -13
- package/src/client/__tests__/compose-launch.test.ts +120 -0
- package/src/client/__tests__/compose-shortcuts.test.ts +141 -0
- package/src/client/__tests__/feed-video-player.test.ts +44 -0
- package/src/client/__tests__/site-header-nav.test.ts +250 -0
- package/src/client/__tests__/sortable-list.test.ts +6 -2
- package/src/client/__tests__/thread-context.test.ts +117 -0
- package/src/client/collection-form-bridge.ts +109 -4
- package/src/client/collection-page-actions.ts +3 -1
- package/src/client/collection-picker-order.ts +104 -0
- package/src/client/components/__tests__/{jant-collection-sidebar.test.ts → jant-collection-directory.test.ts} +91 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +40 -18
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1285 -66
- package/src/client/components/__tests__/jant-compose-editor.test.ts +254 -19
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +127 -1
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +237 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +326 -0
- package/src/client/components/__tests__/jant-post-menu.test.ts +372 -2
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +61 -5
- package/src/client/components/__tests__/jant-settings-general.test.ts +29 -28
- package/src/client/components/__tests__/jant-text-preview.test.ts +206 -0
- package/src/client/components/collection-manager-types.ts +18 -1
- package/src/client/components/compose-types.ts +48 -0
- package/src/client/components/jant-collection-directory.ts +1448 -0
- package/src/client/components/jant-collection-form.ts +65 -17
- package/src/client/components/jant-command-palette.ts +544 -0
- package/src/client/components/jant-compose-dialog.ts +2490 -375
- package/src/client/components/jant-compose-editor.ts +420 -75
- package/src/client/components/jant-compose-fullscreen.ts +59 -8
- package/src/client/components/jant-media-lightbox.ts +373 -18
- package/src/client/components/jant-nav-manager.ts +624 -286
- package/src/client/components/jant-post-menu.ts +451 -126
- package/src/client/components/jant-repo-picker-types.ts +39 -0
- package/src/client/components/jant-repo-picker.ts +799 -0
- package/src/client/components/jant-settings-avatar.ts +16 -2
- package/src/client/components/jant-settings-general.ts +137 -35
- package/src/client/components/jant-text-preview.ts +239 -55
- package/src/client/components/nav-manager-types.ts +25 -9
- package/src/client/components/settings-types.ts +13 -2
- package/src/client/compose-bridge.ts +428 -68
- package/src/client/compose-launch.ts +41 -4
- package/src/client/compose-shortcuts.ts +77 -5
- package/src/client/feed-video-player.ts +374 -0
- package/src/client/image-processor.ts +53 -127
- package/src/client/media-lightbox-events.ts +1 -0
- package/src/client/media-scroll-hint.ts +66 -0
- package/src/client/multipart-upload.ts +4 -1
- package/src/client/palette-shortcuts.ts +35 -0
- package/src/client/search-rank.ts +74 -0
- package/src/client/settings-bridge.ts +7 -0
- package/src/client/site-header-nav.d.ts +1 -0
- package/src/client/site-header-nav.js +139 -51
- package/src/client/sortable-list.ts +2 -0
- package/src/client/thread-context.ts +54 -11
- package/src/client/tiptap/__tests__/block-insertion.test.ts +65 -0
- package/src/client/tiptap/__tests__/footnotes.test.ts +692 -0
- package/src/client/tiptap/__tests__/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} +284 -1151
- package/src/i18n/locales/public/en.ts +1 -0
- package/src/i18n/locales/public/zh-Hans.po +2327 -0
- package/src/i18n/locales/public/zh-Hans.ts +1 -0
- package/src/i18n/locales/public/zh-Hant.po +2327 -0
- package/src/i18n/locales/public/zh-Hant.ts +1 -0
- package/src/i18n/locales/settings/en.po +1622 -0
- package/src/i18n/locales/settings/en.ts +1 -0
- package/src/i18n/locales/settings/zh-Hans.po +1622 -0
- package/src/i18n/locales/settings/zh-Hans.ts +1 -0
- package/src/i18n/locales/settings/zh-Hant.po +1622 -0
- package/src/i18n/locales/settings/zh-Hant.ts +1 -0
- package/src/i18n/middleware.ts +37 -24
- package/src/index.ts +4 -5
- package/src/lib/__tests__/collection-groups.test.ts +17 -0
- package/src/lib/__tests__/constants.test.ts +2 -24
- package/src/lib/__tests__/csp-builder.test.ts +68 -0
- package/src/lib/__tests__/display-text.test.ts +27 -0
- package/src/lib/__tests__/embed-providers.test.ts +104 -0
- package/src/lib/__tests__/embed-render.test.ts +60 -0
- package/src/lib/__tests__/feed.test.ts +125 -63
- package/src/lib/__tests__/hosted-signin.test.ts +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 +20 -3
- 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 +33 -27
- package/src/lib/__tests__/tiptap-render.test.ts +224 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +51 -0
- package/src/lib/__tests__/video-playback.test.ts +48 -0
- package/src/lib/__tests__/view.test.ts +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 +2 -1
- package/src/lib/collection-paths.ts +40 -0
- package/src/lib/constants.ts +3 -24
- package/src/lib/csp-builder.ts +99 -0
- package/src/lib/decorative-quote-mark.ts +11 -0
- package/src/lib/display-text.ts +56 -0
- package/src/lib/embed-providers.ts +289 -0
- package/src/lib/embed-render.ts +151 -0
- package/src/lib/env.ts +70 -1
- package/src/lib/excerpt.ts +11 -2
- package/src/lib/feed.ts +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 +180 -13
- package/src/lib/summary.ts +125 -18
- package/src/lib/theme.ts +119 -0
- package/src/lib/timeline.ts +63 -42
- package/src/lib/tiptap-render.ts +265 -126
- package/src/lib/tiptap-to-markdown.ts +6 -329
- package/src/lib/upload.ts +97 -6
- package/src/lib/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 +197 -43
- 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/readiness.ts +1 -1
- package/src/runtime/site.ts +9 -1
- package/src/services/__tests__/collection.test.ts +137 -54
- package/src/services/__tests__/github-app-installations.test.ts +181 -0
- package/src/services/__tests__/github-sync-classify.test.ts +189 -0
- package/src/services/__tests__/github-sync-push.test.ts +159 -0
- package/src/services/__tests__/media.test.ts +444 -7
- package/src/services/__tests__/navigation.test.ts +271 -14
- package/src/services/__tests__/post-timeline.test.ts +31 -21
- package/src/services/__tests__/post.test.ts +83 -9
- package/src/services/__tests__/settings.test.ts +43 -21
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +10 -7
- package/src/services/collection.ts +591 -193
- package/src/services/custom-url.ts +34 -7
- package/src/services/export-theme/assets/client-site.css +2 -0
- package/src/services/export-theme/assets/client-site.js +145 -0
- package/src/services/export-theme/layouts/_default/alias.html +49 -0
- package/src/services/export-theme/layouts/_default/baseof.html +21 -0
- package/src/services/export-theme/layouts/_default/list.html +104 -0
- package/src/services/export-theme/layouts/_default/rss.xml +160 -0
- package/src/services/export-theme/layouts/_default/single.html +15 -0
- package/src/services/export-theme/layouts/archive/list.html +31 -0
- package/src/services/export-theme/layouts/collection/single.html +19 -0
- package/src/services/export-theme/layouts/collections/list.html +111 -0
- package/src/services/export-theme/layouts/featured/list.html +28 -0
- package/src/services/export-theme/layouts/index.html +41 -0
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +107 -0
- package/src/services/export-theme/layouts/partials/footer.html +32 -0
- package/src/services/export-theme/layouts/partials/head.html +52 -0
- package/src/services/export-theme/layouts/partials/header.html +115 -0
- package/src/services/export-theme/layouts/partials/media-gallery.html +246 -0
- package/src/services/export-theme/layouts/partials/pagination.html +55 -0
- package/src/services/export-theme/layouts/partials/post-card.html +127 -0
- package/src/services/export-theme/layouts/partials/reply.html +91 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +82 -0
- package/src/services/export-theme/layouts/post/list.html +129 -0
- package/src/services/export-theme/styles/main.css +1981 -0
- package/src/services/export-theme/theme.toml +12 -0
- package/src/services/export.ts +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 +658 -195
- package/src/services/search.ts +3 -0
- package/src/services/settings.ts +29 -15
- package/src/services/site-admin.ts +26 -6
- package/src/services/upload-session.ts +69 -34
- package/src/style-cjk-jp.css +3 -0
- package/src/style-cjk-kr.css +3 -0
- package/src/styles/components.css +160 -59
- package/src/styles/fonts/et-book/et-book-bold-line-figures.woff +0 -0
- package/src/styles/fonts/et-book/et-book-display-italic-old-style-figures.woff +0 -0
- package/src/styles/fonts/et-book/et-book-roman-line-figures.woff +0 -0
- package/src/styles/fonts/et-book.css +31 -0
- package/src/styles/fonts/latin.css +1 -0
- package/src/styles/fonts/noto-serif-jp/400/057a6a98bda7fe57105ddaa99ec82015.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0acd1fe2b2ea1ad1bfee7ae1fa139d27.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0c2ab6b295e55f356f8020d4e7747522.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0c5f9492af03a4fa42c784de94649de1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0d9a936885a4c39077438effd3779cbd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/0e645da524f7cfc0e8c3c03fb2b08428.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/135e83b403475c5dc9e49b43501a5b84.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/13b2c53b30e6a3e4de7132dbc18dbdfc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/15d95680dc31cc6ce20e0a5464106dc4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/1733f27476507ca68b68a803bc533cc2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/188c2db794f3dd7a45889ddbc81da9bb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/1cf737900dd49c2e88f1b3221a82a602.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/233d3a685ee18276b319363474599d47.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/284b53bbefb06924cf236d24c8ed5641.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2a56eaf19d1d38a6b57e2a388f733676.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2abfbab82b6a7c04426afc054d2464da.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/2e6f4bb71ef6b38765d51acc5a79f638.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/31194d303a67561926a544ed0e072aee.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/3623d96698132c61e861f984ce11cc23.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/39211b02f24c69cbd1b54f6d41b01933.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/3e24063b19abd2b051af986a6870046d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/4410f876d27c9821839474b6b1059fd7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/479329a39a3eb6c0047e6b0981919855.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/527ff336279465617aafbafe1a830632.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/56b40518ea0608e62826bd44ee27f5f9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5acc7f736217259db79a42bf44241c48.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5c05f8b75c9d7f25b3c04ca711cf43b3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/5e0124c7265f1b4cf0fc797b94efbebc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/615a7e0d63f257109d599b2a1977de68.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/61a2c80d0c924d5a6187c02e8d1d1642.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/62fc94f8d4c5a750b7f25e7387539910.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/639c7c6b0b0c27c738702741cfa4b8c0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/68a3a4bf337f8a0722be76676e20b850.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/6abdd163d2c4b85698db8aa1ce149361.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/73b794d885b88f7befb7ea8ab1780a15.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/743f290baf027d4626a86e22f3d44600.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/757019c01c5e2c2b0f54293cea3b5636.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/76f117a3baacda304a71965a17266a13.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/77747f17625a259fb405b7bbdd84ac4b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/788498548ddfb710d6ef3c7bff79fa97.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/78ea6c40923ce95367e3517dd1e5a849.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/7f9a6c9286b68de9c72b0024f7beeb40.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/823d1bb11097331238d103c7f72138dc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/888ba53510213d5d1f6a7deb60e569b6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/892aa49b2529c89bb4076d4aa51fe30f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/95ff41191c76ef893ac70a1043e95e44.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/96c75862c8cec51a7c05ff025fea86cd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/9b21e8244f5930c48ad5073f83777b6c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/9fce02d1a09f464c36c9fcfb14a354c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a0a6755c7e3f9e4cf03387027dd9f16c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a4475c442c6b259354c9eda62e64e7df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a4a39b5f048671aa37c2b2a9581ab08a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a534449d67561ac9b06489f64e57ad98.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/a919a8c6fec6d7e8bb21177965f2e9d7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ad67113f88b59582991cb0c5d33ea19f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/adb6e613fb99c6dd660b727101f554a8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/adc4a98a89870ef984ee8f4b57c8a3a5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/b12379ab782468d725519cd07a7d15bc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/b55486f8a459c838fe329d4e79a8c211.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bb0b15492b8cdbbec57c0bcfa0aa9241.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bedc74b423b7293b6ad0bdecc61c42cc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/bfb00d4a4c48661bd0be99f300a0faae.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c1b0df29ae41d764904df84e9ac83d1e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c42c67070cfe99cf823df92d81c7fa6e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c4d749e45ecd5a5aed5a0bb3ebfd355d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/c96d83978add28b356c22c4c84916733.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/cb7ccd6494256f7a2977b7c2b0225592.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ce41d70ce6a069a498525c9e15c45cf2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/d509ee5c8241bc7c3de2039d75564fa5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/d675d717cd329bfd4c0524f76ae1579c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/dc8f6256445e68199540be9ade33529f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e8c15be62faf978d208925e79ea6a10d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e91c4d941ed9f5c2f3e27f205a3a225e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e9c566be7a5d38a9085225f7372bc82b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/e9ebc567a711eeb29019ddae3e0ce7fe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/ecb8875a56c7b038b35432fda41ae128.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f1661731474b78bdf81114daf10b254f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f2c58dee206ba9355046fc23d05491f7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f50ac27ea4358d67fdda403c2bb52467.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f55334112f8e8be82d65db29887a663f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f740e93f3c0277ecc616594103bca683.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f7633b5af033d76ff2fb3c3c266d77c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/f7d8468cba2335a83ee414ea68291bab.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/fc9ae5b600fb711f2d67e93ce768cba4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/400/fd1bceb55d3e0183ac2454b8532fec7d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/02611a045a7fe83a12014e3debc9f731.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/0f5e1a18987dbc84ca05188c129e1936.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/112743a4ab5fdd1498dfdf2b11336380.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1211f03d3ff5759a702631445793f60e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/147b24c2f08d03bbed30887d4cb3311c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/16663e567f81f4725a1522f37e18f71f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1824321da801e6257b902f3d1d09054c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1a3a92c7c060a71a6c35819b9ebcdbb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/1fba7ec0e412e911bf31841de5a8a4e4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/28a97af9ab9d38983d20f39ff09840e1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2bfaadaf3479c72286248e6de0be0ec9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2c8c55e4cec85ce77e95cac9d330f5cf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2ed9981d2e8983365bd051159b976b6d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/2f4c633e923ba30c6ba376367379fc91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/349965eee0a9b6c984a319ab96a4ece9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/36cda3eae13370b934bba4429fab9078.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/3aa13f1843d7c8fa16109b6395a9a29d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/3e2c06bdd9dcab29aeaeb0cfb7fa608a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/424123f9371ba357087363263eeafcda.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/43b3bbf43055c3e65db9cdf2c5b18c1c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/44704471b60c18b3ecec5300a88f1102.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/46604090efbb99c2db1800928af0a239.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/46998df85d31e629c0142f0556f5e7c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/49902f1c41b281f8eb3771abdd4dccb8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/4db4f31a16965baa42cb7dad667a8f04.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/572b9e28f1e6d89b765a16d809db84a1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/5921cef36750ff6092d94b83a802823b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/59e95d8b5332dcdae894a19946ab767d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/5ec349ff62b9ee5fe0c6dae867704dc4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/601f812e8b1eafecd5ba585a6a6d7962.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/6025ddaf99d86cc8bfd781006b19fbd6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/60f6e28b3f0400b0e8fe32c3c7116102.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/64a404d675f1d726f0891b7a80794595.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/7158022889c6c177062ac85036e7af10.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/7e38ca789a9e76ac91d9256374e154f0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/815a0b797465190f60d8b1a04656c42e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8726558cf297cbda831cc19514897205.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8855472f3c02f6c7ebb3216617ebe4af.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/8e231d707f0c4f8e3cde90a6b52a79aa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9064c12fd72c7aba235d8c3881755b91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/911b9e53e9814de2998c60bf3115f560.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/92fdc376bce277874e75db666f175b44.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/930a23430f0eb64480d7fe5f82834e21.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/93bed0e5f2dd5a21cf73304fcfbc149d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/93f3ea6918533d96d9c252378b9a4af0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/95a0951d2a2722ff773a1a45e8c474d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/982cd1765ca6bbb3e6547e47834d5148.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9b0c3caac458c7bc9c9133e40405ddea.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/9f3bdab4025dc7c5ba1a5871e62e8918.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a00e94d4f04df6aa659db9c4954c7efe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a2b0597837e382aca19919c4b74e58c8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a373e85c96aa0dc303092429fb837e51.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a6e044424780a57610833cc856c15bf5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/a7c58070d68dc1724e9d4e781afb78db.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/accc43762ad05edfbff66ba1380d192a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ad47bcd6d4663026d648c132d969318d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/af57a2e8c72f9ba0efb1af771d32c124.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b0ae5f374e43dac992a4a5d334cebc0b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b91234c10fd8b8c8abc88e03afe66a1f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/b9317198f118a1dfd8ddf2b82ec028f3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ba825ae79b1a6f7e0cce5215fcb5c96f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc6183ac08f0fac78c46f80c10cf7c92.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc8ccea5abf6598cf3cfa97eb59804bb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bc95a792cbca6639214c9b0da13392ff.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/bcfa62f35731856246c146d3a6932bf3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c063897793f593eb26d6ff7b7baaba18.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c6c2971ad1f3221f6cf84028aa0f477e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/c97f41eef722121d86f55d553c056a39.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/ca62704509932d3232d62918de97af3f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d1f064825fa5784b5c930652bd831cce.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d2faabcedd19f016e7b21bce073c0ec8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d320171e57480510f87dbbc7d5264b0a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d3a72a99d365dddfbca8d017a8011368.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d42aafa0f246ad3b4c16fe96d3a3a432.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d4aaf23c13ae808a4bed617afd13aa07.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d6bb686bddfbe8f38a36a68e609a8667.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/d9020ff69a83b2a6ee1f42ae480f7db0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/da90a9012ab2d98f759e3fa0820ef502.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/df10d94bea357a43313e20da5d84bd30.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e34b2b141e472dc776c86fdf8eea23b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e444fd88f1390636e603d0d681538ac8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/e79898628283edc27180fc39d0d769c1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/eaf332445a40942928e5f5750c1c3116.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/f91cd722855f4269256eae1187df64ec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fc895f5ce66b656f4a933097bf2a8775.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fca6720fd14c467d29a90f18ef3859b9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/700/fd1bb507bcbf04856eeb7f7fd47ea579.woff2 +0 -0
- package/src/styles/fonts/noto-serif-jp/noto-serif-jp.css +3349 -0
- package/src/styles/fonts/noto-serif-kr/400/033466ef683afe931f7f520cfb42d928.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/05da12edb9d52210581dc6ec4541031f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/0968e4861204b51f62a2f8e9f15dd5e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/0d5dec931dc885f07fe5cd5af8bed675.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/1076f0f6f66d28d7a2f16427faad4413.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/12ef1ba76bd20b004b865266a1aa70b3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/152395634a207579552f8cb54db88599.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/1a08931435f885e707edb85978ea3bb6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/31a197213ae61d7043c81013f52d10d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/37dbd564820cce2f7b3331e442da0df3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/37dc8505b20b37a2477a9121b16d44a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/3ac0a07878cacdc98bb889f40f5970d3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/491bbba374093fff045f55803a22bfef.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/4d38e56738156625329d93bd5c8cc74a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/57e9a86651366c1ba299e47aa7e358c2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/57f89adeae55aa7fe2add3fc1801ce03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/5df9ce98c75f8c50fb4fff7f01a23925.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/60020a830d809c26970cf8e48ba1eeda.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/60bcfa3ea7446eb42394aab02d28be40.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/63fafcb069520613d0ea35ad3e6b1e42.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/65a60d87c64228d258a123cbe85f5f31.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/6968e889e18891d912809fe484c2e745.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/70714891ad3fbfc3d5f10a8669dacc5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/761d84afca8d7f34eebefe538adba827.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/76e07530323418ec723c5d7634c9bcca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/773819b7b9b8fd404a929867c0fd677e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7817dd16805145d8538ad57590f69f5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7b59f0ec7792b18458dc5a361e37884c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/7c3945788a689a69356c1a622d69d48b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/835b6505bb9eea9678925a1fa885353d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8b5c9ef81159f31d2ab35f45ca4373e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8b91c7c2ed390f1278b9befa3aa87233.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/8fb45117a62d92dce44a80f0b729ead5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/90b7848e9b1623b77bdcf155e90b839c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/985d4d41afa0934a4eb2de35473fb9a2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9bb38fd05201de666b88b82d56a386f7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9eff2ab2a9d2ced47ab761bf6fdb11ec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/9f1963fe8d4d6878d717d872a8f3fdb5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a0d00fe4816c95a8c7dffd445ef00b03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a0f199077fa1a33bc2a1e01c64de7fe6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/a7c35e42a659347e490c3cb7983b2e7f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/aa662092cf249707123ca4d575e2764b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/aae1eda193285ab817a2d1b408440326.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b0cb664cb2e1371efda8943c0b7dcd1c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b0d1cdced482352cf0d3ae58638aacb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/b320f5d185b2cff933ac549c184031c5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ba9c59d7dfa4494db1bb764ada81467d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bab547459d514f46206e340c4bb2dc88.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bcc39eda837bb7a7a3d37c8c60fffb81.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bd73264d7f98776708d5d6f3c9b78fcc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bed610b217d500f5975cfc9fe6157570.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/bf8901f8f11d4f433ea17dabc9370ea6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c4143bb9f2fe77b6ccf20088a8904650.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c7ff3f6bbdcd5f604b7343602ab904df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/c945c62368357d05a53206620460fb30.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ce022e18a1377ac509443c3c3790b431.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/cf9cffe56636322f62b40d61130fbc5e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d20d8944bc0b85f5b2aae4b24f343516.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d36644d6502527e1fff205d0c7eca434.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/d40ab99c7a38026f411c8f112f742b48.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/dc6e234ded795e91f76d6647f628fbf0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/dd0cfdfdac0866e66d587e2b5a9e9961.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/de304c8a02e45ded4f8dcc479d167198.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/e8c364c16daa04835bf32d293d2598db.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ec86e23683052da5cfdc3b77641bd15a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/eda1b0cb6d1719dd9bedcf3216a9e8de.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/ee6fce20b420a480714607c66d7f97e5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/eee3836d6ac17ebb2c450bbcbc9db121.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/f5738255e92d8dd34a46d1bcdf4c4074.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/fa6e58ce4b52695e7ae19bbea6336ec8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/400/fd2069e6e8588c70b4f11364093b81f2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/04b8bde59cff68eeee74fb1914eb09c7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/05ac821472e235943ed1435e4bb8ecad.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/074c1f8483d5a4d8c45c8c5f3e4cbb32.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/1380210a722ac9aeef54005ad7b015c9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/17c7f5d0a45e92ede0e5dec3a2c77efa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/1be602ad456b0d75902d352116cb35fd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/2612c60f9dc5459ac42763591240a1d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/2ab8cf1f23a5ac7939a7876054e711a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/34e6750e00c3a911ef87dc66b336594d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/36f3df4730cfca4fc77fe52b52e6a126.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/37887deb7a9c466cf2af6ee7ff372ea6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3aba89c4d4281553078de4622b498cbb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3d88558097559d775231194d43745ba7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/3dc3e8c35524cad2d1236f3393104ccf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/45140742dfa8686b58db2899ce89f89a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/469d2754e315e77270c12fa0ab28026c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4c0760284569b87d8f2290c324953973.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4cba92c942694bdc479bc54101bb1aa4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/4e7d263c30bb48604069a3b4404d4eeb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/5279c7e465d9871edf51fd02e691eee7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/54bfdb6f21f9d6191e5100580239e14f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/56be5e32fd01668fe5ba814d94905839.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/615b38e0b5bb3ea68426f44f47706e6f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/65e9c4585d71bf48a5c62f367010da16.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/6b75213eb0be40ce84241eb2bb438a2e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7520ca6ca8c60eb1e62d50e71c8d30f1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/759a647b77791b1e98f99bc0ab5317a7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/76e51630143b95b6322ef93ad330da7a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/79aa7c8c842c4a27cf57c0a3a1ffca5a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7cc72fd2c9105560422b6a67c6162945.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7d0ea0690e432462f4d05a23d720ec19.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/7dffa5c1bec57e0903fd62357401ff1a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/82f6ccc063960eed1cdfd1d61ada8862.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/845b4b67564d62cf5cad242f7ac08ea5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/84c8e7bc0931008ed91f44ed12dfea94.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/887a11290ba78b1e66c6d2f67043005e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/88db1d042074fb6e66821ffc10941930.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/8dc18c0cebe6aa4bf4c45dbb831048ab.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/919a879bd2d580d8491a31a449390689.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/91da6cb174bebfb96976e2f1316be2fe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/934dfdbed2bb2c4b6129199d699a34fa.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/97d7a17234f2b5030ae9697ae00aded2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/995c4fda62d25c3b79dd5987737df6ae.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/9d0afa53dc2f92ba42024f55f11f6779.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/9e13a47242926f1cd7d68c08d9ef8889.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a379ada89abd750c587ded29f77e731c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a48be56ce5d3a99dc8f8331398004412.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a806759e72987951d6d08b7cbdf36a1b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a88481ec8bc7be11cb66e46781a79bb9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/a9a016d4a93409f65278327e8a8bb38d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/ab92d41062a7644faa45f50bf384454a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b326a8a9c33b554db570da94f60bc380.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b6117ff2993b11bb1fdc7ea3588a010c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/b61982951bd51b724143c30dfaaa9fe9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c0098958e20db68cab90097b5e62516f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c4bfaa5e50798246e3770718b7a7c84a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/c84598999133455503042e06f4ab79cb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/ca9a533988d7019597a60d4e17127e0c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/cf2b28f90f47276f7e2688a65e88a101.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/d689b1861d7e4377dd72ad3013482612.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/d9047070d72a816b3dba9d40c2d85e69.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/da04549f3f4ed28076b01b8cd710d313.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/da7af303f8c645f9a9dbae0e6e32dd35.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/df9568257eb29b156449fdd4bec5ec76.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e067cd0ed76c90cd0a93c9339253f20b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e08b07772e7bed3cec2832d43f7fd339.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e12150d5a39b30be8f567968c7a527b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e53fcb2381eee345db4f6f973dd95a3e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/e5bd313ef81f687d398aacb11cec3069.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/eb5afb3d952b8593782caec6026514b6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/edd6a4f608d04fc0351d7688cfc321e4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/f9695c6c4df2bf6bc03045ff79d4f01f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/fada6eaa68ff8816afe43d2a36c5423e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/700/fbcc4bf5367218951172bdee6f77d7a6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-kr/noto-serif-kr.css +2601 -0
- package/src/styles/site-media.css +823 -0
- package/src/styles/tokens.css +292 -21
- package/src/styles/ui.css +4188 -1857
- package/src/types/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 +10 -2
- 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 +151 -78
- package/src/ui/pages/CollectionsPage.tsx +27 -18
- package/src/ui/pages/ComposePage.tsx +15 -10
- package/src/ui/pages/FeaturedPage.tsx +16 -11
- package/src/ui/pages/HomePage.tsx +15 -10
- package/src/ui/pages/PostPage.tsx +5 -1
- package/src/ui/pages/SearchPage.tsx +93 -43
- package/src/ui/pages/ThemeSamplePage.tsx +758 -520
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +72 -0
- package/src/ui/shared/CollectionDirectory.tsx +215 -34
- package/src/ui/shared/CollectionsManager.tsx +155 -75
- package/src/ui/shared/DecorativeQuoteMark.tsx +12 -9
- package/src/ui/shared/MediaGallery.tsx +350 -256
- package/src/ui/shared/Pagination.tsx +36 -25
- package/src/ui/shared/PostFooter.tsx +139 -96
- package/src/ui/shared/__tests__/media-gallery.test.ts +27 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +79 -28
- package/src/ui/shared/__tests__/post-footer.test.ts +42 -17
- package/src/ui/shared/collection-management-labels.ts +109 -33
- package/src/ui/shared/navigation-labels.ts +78 -8
- package/bin/lib/site-localize-media.js +0 -427
- package/dist/client/_assets/client-auth.js +0 -3260
- package/dist/client/_assets/client.css +0 -2
- package/dist/client/_assets/client.js +0 -380
- package/src/__tests__/site-localize-media.test.ts +0 -150
- package/src/client/components/jant-collection-sidebar.ts +0 -816
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/zh-Hans.po +0 -3197
- package/src/i18n/locales/zh-Hans.ts +0 -1
- package/src/i18n/locales/zh-Hant.po +0 -3197
- package/src/i18n/locales/zh-Hant.ts +0 -1
- package/src/routes/feed/rss.ts +0 -216
- package/src/types/lingui-react-macro.d.ts +0 -34
- /package/dist/client/_assets/chunks/{heic-to-XcUDQvtx.js → heic-to-DUUaO23q.js} +0 -0
- /package/dist/client/_assets/chunks/{module-ChVQstFd.js → module-DcsAZQZ_.js} +0 -0
- /package/dist/client/_assets/chunks/{native-CR5HLOyf.js → native-DpcrFAPh.js} +0 -0
- /package/dist/client/_assets/{client-cjk.css → client-cjk-B7Z0snDu.css} +0 -0
- /package/dist/client/_assets/{client-cjk-tc.css → client-cjk-tc-BesJYrb2.css} +0 -0
package/src/services/post.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import {
|
|
10
10
|
eq,
|
|
11
11
|
and,
|
|
12
|
+
or,
|
|
12
13
|
type SQL,
|
|
13
14
|
type SQLWrapper,
|
|
14
15
|
isNull,
|
|
@@ -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 {
|
|
@@ -139,8 +147,18 @@ export interface CollectionFeedEntry {
|
|
|
139
147
|
collectedAt: number;
|
|
140
148
|
}
|
|
141
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
|
+
|
|
142
159
|
export interface PostService {
|
|
143
160
|
getById(id: string): Promise<Post | null>;
|
|
161
|
+
getBodyContent(id: string): Promise<PostBodyContent | null>;
|
|
144
162
|
getBySlug(slug: string): Promise<Post | null>;
|
|
145
163
|
suggestSlug(input: {
|
|
146
164
|
title?: string;
|
|
@@ -164,6 +182,24 @@ export interface PostService {
|
|
|
164
182
|
deps: PostAttachmentDeps,
|
|
165
183
|
summaryConfig?: SummaryConfig,
|
|
166
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[]>;
|
|
167
203
|
update(
|
|
168
204
|
id: string,
|
|
169
205
|
data: UpdatePost,
|
|
@@ -184,6 +220,11 @@ export interface PostService {
|
|
|
184
220
|
* @param deps - Media service and optional storage driver for file cleanup
|
|
185
221
|
*/
|
|
186
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>;
|
|
187
228
|
getThread(rootId: string): Promise<Post[]>;
|
|
188
229
|
updateThreadStatusAndVisibility(
|
|
189
230
|
rootId: string,
|
|
@@ -259,24 +300,8 @@ function isValidSlug(value: string): boolean {
|
|
|
259
300
|
return SLUG_RE.test(value);
|
|
260
301
|
}
|
|
261
302
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
let current: unknown = err;
|
|
265
|
-
while (current) {
|
|
266
|
-
const msg = String(current);
|
|
267
|
-
if (
|
|
268
|
-
msg.includes("UNIQUE constraint") ||
|
|
269
|
-
msg.includes("SQLITE_CONSTRAINT")
|
|
270
|
-
) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
current =
|
|
274
|
-
current instanceof Error && current.cause !== current
|
|
275
|
-
? current.cause
|
|
276
|
-
: undefined;
|
|
277
|
-
}
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
303
|
+
// Re-export shared constraint detection — see db/dialect.ts
|
|
304
|
+
import { isUniqueConstraintError } from "../db/dialect.js";
|
|
280
305
|
|
|
281
306
|
function hasNonEmptyText(value: string | null | undefined): boolean {
|
|
282
307
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -349,8 +374,10 @@ function ensurePostRating(
|
|
|
349
374
|
function assertPostFormatShape(data: {
|
|
350
375
|
format: Format;
|
|
351
376
|
title?: string | null;
|
|
377
|
+
body?: unknown;
|
|
352
378
|
url?: string | null;
|
|
353
379
|
quoteText?: string | null;
|
|
380
|
+
hasAttachments?: boolean;
|
|
354
381
|
}): void {
|
|
355
382
|
const hasTitle = hasNonEmptyText(data.title);
|
|
356
383
|
const hasUrl = hasNonEmptyText(data.url);
|
|
@@ -363,6 +390,18 @@ function assertPostFormatShape(data: {
|
|
|
363
390
|
if (hasQuoteText) {
|
|
364
391
|
throw new ValidationError("Notes can't include quoted text.");
|
|
365
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
|
+
}
|
|
366
405
|
return;
|
|
367
406
|
}
|
|
368
407
|
|
|
@@ -497,6 +536,87 @@ export function createPostService(
|
|
|
497
536
|
return [...new Set(collectionIds)];
|
|
498
537
|
}
|
|
499
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
|
+
|
|
500
620
|
function buildCollectionMembershipCondition(
|
|
501
621
|
collectionIds: readonly string[],
|
|
502
622
|
): SQL<unknown> {
|
|
@@ -631,6 +751,25 @@ export function createPostService(
|
|
|
631
751
|
return conditions;
|
|
632
752
|
}
|
|
633
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
|
+
|
|
634
773
|
function getCursorSortTimestamp(row: typeof posts.$inferSelect): number {
|
|
635
774
|
return row.status === "draft" ? row.updatedAt : (row.lastActivityAt ?? -1);
|
|
636
775
|
}
|
|
@@ -790,6 +929,9 @@ export function createPostService(
|
|
|
790
929
|
quoteText: row.quoteText,
|
|
791
930
|
summary: row.summary,
|
|
792
931
|
rating: ensurePostRating(row.rating, Error),
|
|
932
|
+
previewImageKey: row.previewImageKey,
|
|
933
|
+
previewKind: row.previewKind,
|
|
934
|
+
previewProvider: row.previewProvider,
|
|
793
935
|
replyToId: row.replyToId,
|
|
794
936
|
threadId: row.threadId,
|
|
795
937
|
deletedAt: row.deletedAt,
|
|
@@ -981,6 +1123,24 @@ export function createPostService(
|
|
|
981
1123
|
return rows.map((row) => row.collectionId);
|
|
982
1124
|
}
|
|
983
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
|
+
|
|
984
1144
|
function buildRollbackUpdate(
|
|
985
1145
|
post: Post,
|
|
986
1146
|
collectionIds: string[],
|
|
@@ -1018,6 +1178,20 @@ export function createPostService(
|
|
|
1018
1178
|
return hydratePost(result[0]);
|
|
1019
1179
|
},
|
|
1020
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
|
+
|
|
1021
1195
|
async getBySlug(slug) {
|
|
1022
1196
|
const resolved = await resolvedPaths.resolve(slug);
|
|
1023
1197
|
if (!resolved || resolved.kind !== "slug" || !resolved.postId) {
|
|
@@ -1073,6 +1247,14 @@ export function createPostService(
|
|
|
1073
1247
|
ELSE 1
|
|
1074
1248
|
END`;
|
|
1075
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
|
+
|
|
1076
1258
|
const baseQuery = db
|
|
1077
1259
|
.select()
|
|
1078
1260
|
.from(posts)
|
|
@@ -1082,29 +1264,31 @@ export function createPostService(
|
|
|
1082
1264
|
let query =
|
|
1083
1265
|
filters.featured || filters.sortOrder === undefined
|
|
1084
1266
|
? baseQuery.orderBy(
|
|
1085
|
-
desc(
|
|
1086
|
-
filters.featured
|
|
1267
|
+
desc(pinnedSortExpr),
|
|
1268
|
+
filters.featured
|
|
1269
|
+
? desc(featuredSortExpr)
|
|
1270
|
+
: desc(sortTimestampSortExpr),
|
|
1087
1271
|
desc(posts.id),
|
|
1088
1272
|
)
|
|
1089
1273
|
: filters.sortOrder === "oldest"
|
|
1090
1274
|
? baseQuery.orderBy(
|
|
1091
|
-
desc(
|
|
1092
|
-
asc(
|
|
1275
|
+
desc(pinnedSortExpr),
|
|
1276
|
+
asc(sortTimestampSortExpr),
|
|
1093
1277
|
asc(posts.id),
|
|
1094
1278
|
)
|
|
1095
1279
|
: filters.sortOrder === "rating_desc"
|
|
1096
1280
|
? baseQuery.orderBy(
|
|
1097
|
-
desc(
|
|
1281
|
+
desc(pinnedSortExpr),
|
|
1098
1282
|
desc(ratingPresence),
|
|
1099
1283
|
desc(posts.rating),
|
|
1100
|
-
desc(
|
|
1284
|
+
desc(sortTimestampSortExpr),
|
|
1101
1285
|
desc(posts.id),
|
|
1102
1286
|
)
|
|
1103
1287
|
: baseQuery.orderBy(
|
|
1104
|
-
desc(
|
|
1288
|
+
desc(pinnedSortExpr),
|
|
1105
1289
|
desc(ratingPresence),
|
|
1106
1290
|
asc(posts.rating),
|
|
1107
|
-
desc(
|
|
1291
|
+
desc(sortTimestampSortExpr),
|
|
1108
1292
|
desc(posts.id),
|
|
1109
1293
|
);
|
|
1110
1294
|
|
|
@@ -1120,7 +1304,7 @@ export function createPostService(
|
|
|
1120
1304
|
const conditions = buildFilterConditions(filters);
|
|
1121
1305
|
|
|
1122
1306
|
const result = await db
|
|
1123
|
-
.select({ count: sql<number>`count(*)`.as("count") })
|
|
1307
|
+
.select({ count: sql<number>`CAST(count(*) AS INTEGER)`.as("count") })
|
|
1124
1308
|
.from(posts)
|
|
1125
1309
|
.where(conditions.length > 0 ? and(...conditions) : undefined);
|
|
1126
1310
|
|
|
@@ -1153,7 +1337,7 @@ export function createPostService(
|
|
|
1153
1337
|
return db
|
|
1154
1338
|
.select({
|
|
1155
1339
|
yearMonth: publishedYearMonthExpr.as("year_month"),
|
|
1156
|
-
count: sql<number>`count(*)`.as("count"),
|
|
1340
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
1157
1341
|
})
|
|
1158
1342
|
.from(posts)
|
|
1159
1343
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
@@ -1173,22 +1357,31 @@ export function createPostService(
|
|
|
1173
1357
|
: undefined;
|
|
1174
1358
|
const rating = ensurePostRating(data.rating);
|
|
1175
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
|
+
|
|
1176
1368
|
assertPostFormatShape({
|
|
1177
1369
|
format,
|
|
1178
|
-
title
|
|
1179
|
-
|
|
1180
|
-
|
|
1370
|
+
title,
|
|
1371
|
+
body,
|
|
1372
|
+
url,
|
|
1373
|
+
quoteText,
|
|
1374
|
+
hasAttachments: data.attachments
|
|
1375
|
+
? data.attachments.length > 0
|
|
1376
|
+
: undefined,
|
|
1181
1377
|
});
|
|
1182
1378
|
|
|
1183
|
-
const body = data.bodyMarkdown
|
|
1184
|
-
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1185
|
-
: (data.body ?? null);
|
|
1186
1379
|
const bodyHtml = body ? renderTiptapJson(body) : null;
|
|
1187
1380
|
const bodyText = body ? extractBodyText(body) : null;
|
|
1188
1381
|
|
|
1189
1382
|
// Generate summary for titled notes with body content
|
|
1190
1383
|
let summary: string | null = null;
|
|
1191
|
-
if (format === "note" &&
|
|
1384
|
+
if (format === "note" && title && body && summaryConfig) {
|
|
1192
1385
|
summary = extractSummary(
|
|
1193
1386
|
body,
|
|
1194
1387
|
summaryConfig.maxParagraphs,
|
|
@@ -1201,18 +1394,45 @@ export function createPostService(
|
|
|
1201
1394
|
let status: Status = requestedStatus ?? "published";
|
|
1202
1395
|
let visibility: Visibility | null = requestedVisibility ?? "public";
|
|
1203
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
|
+
|
|
1204
1417
|
if (data.replyToId) {
|
|
1205
1418
|
const parent = await this.getById(data.replyToId);
|
|
1206
1419
|
if (!parent) {
|
|
1207
1420
|
throw new NotFoundError("Parent post");
|
|
1208
1421
|
}
|
|
1209
1422
|
|
|
1210
|
-
if (
|
|
1423
|
+
if (resolvedPinnedAt !== null) {
|
|
1211
1424
|
throw new ConflictError(
|
|
1212
1425
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1213
1426
|
);
|
|
1214
1427
|
}
|
|
1215
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
|
+
|
|
1216
1436
|
threadId = parent.threadId;
|
|
1217
1437
|
|
|
1218
1438
|
// Replies inherit visibility from the root at read time.
|
|
@@ -1236,6 +1456,14 @@ export function createPostService(
|
|
|
1236
1456
|
let slug: string;
|
|
1237
1457
|
let aliasPath: string | null = null;
|
|
1238
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
|
+
|
|
1239
1467
|
if (data.path) {
|
|
1240
1468
|
const normalized = normalizePath(data.path);
|
|
1241
1469
|
if (isValidSlug(normalized)) {
|
|
@@ -1250,7 +1478,7 @@ export function createPostService(
|
|
|
1250
1478
|
const slugified = slugify(normalized);
|
|
1251
1479
|
slug = await generatePostSlug({
|
|
1252
1480
|
slug: slugified || undefined,
|
|
1253
|
-
title:
|
|
1481
|
+
title: titleForSlug,
|
|
1254
1482
|
idLength: config.slugIdLength,
|
|
1255
1483
|
isAvailable: isSlugAvailable,
|
|
1256
1484
|
});
|
|
@@ -1263,13 +1491,61 @@ export function createPostService(
|
|
|
1263
1491
|
} else {
|
|
1264
1492
|
slug = await generatePostSlug({
|
|
1265
1493
|
slug: data.slug,
|
|
1266
|
-
title:
|
|
1494
|
+
title: titleForSlug,
|
|
1267
1495
|
idLength: config.slugIdLength,
|
|
1268
1496
|
isAvailable: isSlugAvailable,
|
|
1269
1497
|
});
|
|
1270
1498
|
}
|
|
1271
1499
|
|
|
1272
|
-
|
|
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
|
+
}));
|
|
1273
1549
|
|
|
1274
1550
|
try {
|
|
1275
1551
|
if (usesBatchWrites) {
|
|
@@ -1282,14 +1558,14 @@ export function createPostService(
|
|
|
1282
1558
|
format,
|
|
1283
1559
|
status,
|
|
1284
1560
|
visibility,
|
|
1285
|
-
pinnedAt:
|
|
1286
|
-
featuredAt:
|
|
1287
|
-
title
|
|
1288
|
-
url
|
|
1289
|
-
body
|
|
1561
|
+
pinnedAt: resolvedPinnedAt,
|
|
1562
|
+
featuredAt: resolvedFeaturedAt,
|
|
1563
|
+
title,
|
|
1564
|
+
url,
|
|
1565
|
+
body,
|
|
1290
1566
|
bodyHtml,
|
|
1291
1567
|
bodyText,
|
|
1292
|
-
quoteText
|
|
1568
|
+
quoteText,
|
|
1293
1569
|
summary,
|
|
1294
1570
|
rating,
|
|
1295
1571
|
replyToId: data.replyToId ?? null,
|
|
@@ -1333,16 +1609,9 @@ export function createPostService(
|
|
|
1333
1609
|
);
|
|
1334
1610
|
}
|
|
1335
1611
|
|
|
1336
|
-
if (
|
|
1612
|
+
if (collectionInsertRows.length > 0) {
|
|
1337
1613
|
writeQueries.push(
|
|
1338
|
-
db.insert(postCollections).values(
|
|
1339
|
-
collectionIds.map((collectionId) => ({
|
|
1340
|
-
siteId,
|
|
1341
|
-
postId: id,
|
|
1342
|
-
collectionId,
|
|
1343
|
-
createdAt: timestamp,
|
|
1344
|
-
})),
|
|
1345
|
-
),
|
|
1614
|
+
db.insert(postCollections).values(collectionInsertRows),
|
|
1346
1615
|
);
|
|
1347
1616
|
}
|
|
1348
1617
|
|
|
@@ -1360,14 +1629,14 @@ export function createPostService(
|
|
|
1360
1629
|
format,
|
|
1361
1630
|
status,
|
|
1362
1631
|
visibility,
|
|
1363
|
-
pinnedAt:
|
|
1364
|
-
featuredAt:
|
|
1365
|
-
title
|
|
1366
|
-
url
|
|
1367
|
-
body
|
|
1632
|
+
pinnedAt: resolvedPinnedAt,
|
|
1633
|
+
featuredAt: resolvedFeaturedAt,
|
|
1634
|
+
title,
|
|
1635
|
+
url,
|
|
1636
|
+
body,
|
|
1368
1637
|
bodyHtml,
|
|
1369
1638
|
bodyText,
|
|
1370
|
-
quoteText
|
|
1639
|
+
quoteText,
|
|
1371
1640
|
summary,
|
|
1372
1641
|
rating,
|
|
1373
1642
|
replyToId: data.replyToId ?? null,
|
|
@@ -1406,15 +1675,8 @@ export function createPostService(
|
|
|
1406
1675
|
});
|
|
1407
1676
|
}
|
|
1408
1677
|
|
|
1409
|
-
if (
|
|
1410
|
-
await tx.insert(postCollections).values(
|
|
1411
|
-
collectionIds.map((collectionId) => ({
|
|
1412
|
-
siteId,
|
|
1413
|
-
postId: id,
|
|
1414
|
-
collectionId,
|
|
1415
|
-
createdAt: timestamp,
|
|
1416
|
-
})),
|
|
1417
|
-
);
|
|
1678
|
+
if (collectionInsertRows.length > 0) {
|
|
1679
|
+
await tx.insert(postCollections).values(collectionInsertRows);
|
|
1418
1680
|
}
|
|
1419
1681
|
});
|
|
1420
1682
|
}
|
|
@@ -1434,7 +1696,7 @@ export function createPostService(
|
|
|
1434
1696
|
}
|
|
1435
1697
|
|
|
1436
1698
|
// Bump thread root's lastActivityAt when creating a published reply
|
|
1437
|
-
if (data.replyToId && status === "published") {
|
|
1699
|
+
if (data.replyToId && status === "published" && !data.quietReply) {
|
|
1438
1700
|
await recalculateThreadLastActivity(threadId);
|
|
1439
1701
|
}
|
|
1440
1702
|
|
|
@@ -1447,13 +1709,25 @@ export function createPostService(
|
|
|
1447
1709
|
await createAttachmentMediaIds(attachmentInputs, deps);
|
|
1448
1710
|
|
|
1449
1711
|
try {
|
|
1450
|
-
const post = await this.create(
|
|
1712
|
+
const post = await this.create(
|
|
1713
|
+
{ ...data, attachments: attachmentInputs },
|
|
1714
|
+
summaryConfig,
|
|
1715
|
+
);
|
|
1451
1716
|
|
|
1452
1717
|
try {
|
|
1453
1718
|
if (orderedMediaIds.length > 0) {
|
|
1454
1719
|
await deps.media.attachToPost(post.id, orderedMediaIds);
|
|
1455
1720
|
}
|
|
1456
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
|
+
|
|
1457
1731
|
return post;
|
|
1458
1732
|
} catch (error) {
|
|
1459
1733
|
await deps.media.attachToPost(post.id, []).catch(() => undefined);
|
|
@@ -1470,6 +1744,47 @@ export function createPostService(
|
|
|
1470
1744
|
}
|
|
1471
1745
|
},
|
|
1472
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
|
+
|
|
1473
1788
|
async update(id, data, summaryConfig) {
|
|
1474
1789
|
const existing = await this.getById(id);
|
|
1475
1790
|
if (!existing) return null;
|
|
@@ -1479,9 +1794,12 @@ export function createPostService(
|
|
|
1479
1794
|
data.format !== undefined
|
|
1480
1795
|
? ensurePostFormat(data.format)
|
|
1481
1796
|
: existing.format;
|
|
1482
|
-
const nextUrl =
|
|
1797
|
+
const nextUrl =
|
|
1798
|
+
data.url !== undefined ? data.url?.trim() || null : existing.url;
|
|
1483
1799
|
const nextQuoteText =
|
|
1484
|
-
data.quoteText !== undefined
|
|
1800
|
+
data.quoteText !== undefined
|
|
1801
|
+
? data.quoteText?.trim() || null
|
|
1802
|
+
: existing.quoteText;
|
|
1485
1803
|
const nextStatus =
|
|
1486
1804
|
data.status !== undefined
|
|
1487
1805
|
? ensurePostStatus(data.status)
|
|
@@ -1493,11 +1811,26 @@ export function createPostService(
|
|
|
1493
1811
|
const nextRating =
|
|
1494
1812
|
data.rating !== undefined ? ensurePostRating(data.rating) : undefined;
|
|
1495
1813
|
|
|
1814
|
+
const nextTitle =
|
|
1815
|
+
data.title !== undefined ? data.title?.trim() || null : existing.title;
|
|
1816
|
+
|
|
1496
1817
|
assertPostFormatShape({
|
|
1497
1818
|
format: nextFormat,
|
|
1498
|
-
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,
|
|
1499
1826
|
url: nextUrl,
|
|
1500
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,
|
|
1501
1834
|
});
|
|
1502
1835
|
assertDraftPublishedAt(nextStatus, data.publishedAt);
|
|
1503
1836
|
|
|
@@ -1520,19 +1853,27 @@ export function createPostService(
|
|
|
1520
1853
|
}
|
|
1521
1854
|
|
|
1522
1855
|
if (data.format !== undefined) updates.format = nextFormat;
|
|
1523
|
-
if (data.title !== undefined) updates.title =
|
|
1524
|
-
if (data.url !== undefined) updates.url =
|
|
1525
|
-
if (data.quoteText !== undefined) updates.quoteText =
|
|
1856
|
+
if (data.title !== undefined) updates.title = nextTitle;
|
|
1857
|
+
if (data.url !== undefined) updates.url = nextUrl;
|
|
1858
|
+
if (data.quoteText !== undefined) updates.quoteText = nextQuoteText;
|
|
1526
1859
|
if (data.rating !== undefined) updates.rating = nextRating;
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
+
}
|
|
1531
1871
|
|
|
1532
1872
|
if (data.body !== undefined || data.bodyMarkdown !== undefined) {
|
|
1533
|
-
const
|
|
1873
|
+
const rawBody = data.bodyMarkdown
|
|
1534
1874
|
? markdownToTiptapJson(data.bodyMarkdown)
|
|
1535
1875
|
: (data.body ?? null);
|
|
1876
|
+
const normalizedBody = rawBody ? trimTiptapBody(rawBody) : null;
|
|
1536
1877
|
updates.body = normalizedBody;
|
|
1537
1878
|
updates.bodyHtml = normalizedBody
|
|
1538
1879
|
? renderTiptapJson(normalizedBody)
|
|
@@ -1545,7 +1886,7 @@ export function createPostService(
|
|
|
1545
1886
|
// Recompute summary when body, title, or format change
|
|
1546
1887
|
if (summaryConfig) {
|
|
1547
1888
|
const format = nextFormat;
|
|
1548
|
-
const title =
|
|
1889
|
+
const title = nextTitle;
|
|
1549
1890
|
const body =
|
|
1550
1891
|
data.bodyMarkdown !== undefined
|
|
1551
1892
|
? data.bodyMarkdown
|
|
@@ -1572,7 +1913,10 @@ export function createPostService(
|
|
|
1572
1913
|
"Cannot change visibility of a thread reply. Update the root post instead.",
|
|
1573
1914
|
);
|
|
1574
1915
|
}
|
|
1575
|
-
if (
|
|
1916
|
+
if (
|
|
1917
|
+
(data.pinnedAt !== undefined && data.pinnedAt !== null) ||
|
|
1918
|
+
data.pinned === true
|
|
1919
|
+
) {
|
|
1576
1920
|
throw new ConflictError(
|
|
1577
1921
|
"Cannot pin a thread reply. Pin the root post instead.",
|
|
1578
1922
|
);
|
|
@@ -1631,9 +1975,9 @@ export function createPostService(
|
|
|
1631
1975
|
}
|
|
1632
1976
|
|
|
1633
1977
|
// Complex case: cascade + update + collection sync atomically
|
|
1634
|
-
const
|
|
1635
|
-
? await
|
|
1636
|
-
:
|
|
1978
|
+
const existingCollectionTimestamps = needsCollectionSync
|
|
1979
|
+
? await getPostCollectionTimestamps(id)
|
|
1980
|
+
: new Map<string, number>();
|
|
1637
1981
|
let updateResult: (typeof posts.$inferSelect)[] | undefined;
|
|
1638
1982
|
|
|
1639
1983
|
if (usesBatchWrites) {
|
|
@@ -1688,38 +2032,30 @@ export function createPostService(
|
|
|
1688
2032
|
);
|
|
1689
2033
|
|
|
1690
2034
|
if (needsCollectionSync) {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if (removedIds.length > 0) {
|
|
1701
|
-
writeQueries.push(
|
|
1702
|
-
db
|
|
1703
|
-
.delete(postCollections)
|
|
1704
|
-
.where(
|
|
1705
|
-
and(
|
|
1706
|
-
eq(postCollections.siteId, siteId),
|
|
1707
|
-
eq(postCollections.postId, id),
|
|
1708
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1709
|
-
),
|
|
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),
|
|
1710
2043
|
),
|
|
1711
|
-
|
|
1712
|
-
|
|
2044
|
+
),
|
|
2045
|
+
);
|
|
1713
2046
|
|
|
1714
|
-
if (
|
|
2047
|
+
if (nextCollectionIds.length > 0) {
|
|
1715
2048
|
const collectionTimestamp = now();
|
|
1716
2049
|
writeQueries.push(
|
|
1717
2050
|
db.insert(postCollections).values(
|
|
1718
|
-
|
|
2051
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1719
2052
|
siteId,
|
|
1720
2053
|
postId: id,
|
|
1721
2054
|
collectionId,
|
|
1722
|
-
createdAt:
|
|
2055
|
+
createdAt:
|
|
2056
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2057
|
+
collectionTimestamp,
|
|
2058
|
+
position: index,
|
|
1723
2059
|
})),
|
|
1724
2060
|
),
|
|
1725
2061
|
);
|
|
@@ -1779,35 +2115,27 @@ export function createPostService(
|
|
|
1779
2115
|
.returning();
|
|
1780
2116
|
|
|
1781
2117
|
if (needsCollectionSync) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (removedIds.length > 0) {
|
|
1792
|
-
await tx
|
|
1793
|
-
.delete(postCollections)
|
|
1794
|
-
.where(
|
|
1795
|
-
and(
|
|
1796
|
-
eq(postCollections.siteId, siteId),
|
|
1797
|
-
eq(postCollections.postId, id),
|
|
1798
|
-
inArray(postCollections.collectionId, removedIds),
|
|
1799
|
-
),
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
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
|
+
);
|
|
1802
2127
|
|
|
1803
|
-
if (
|
|
2128
|
+
if (nextCollectionIds.length > 0) {
|
|
1804
2129
|
const collectionTimestamp = now();
|
|
1805
2130
|
await tx.insert(postCollections).values(
|
|
1806
|
-
|
|
2131
|
+
nextCollectionIds.map((collectionId, index) => ({
|
|
1807
2132
|
siteId,
|
|
1808
2133
|
postId: id,
|
|
1809
2134
|
collectionId,
|
|
1810
|
-
createdAt:
|
|
2135
|
+
createdAt:
|
|
2136
|
+
existingCollectionTimestamps.get(collectionId) ??
|
|
2137
|
+
collectionTimestamp,
|
|
2138
|
+
position: index,
|
|
1811
2139
|
})),
|
|
1812
2140
|
);
|
|
1813
2141
|
}
|
|
@@ -1824,7 +2152,32 @@ export function createPostService(
|
|
|
1824
2152
|
|
|
1825
2153
|
async updateWithAttachments(id, data, attachments, deps, summaryConfig) {
|
|
1826
2154
|
if (attachments === undefined) {
|
|
1827
|
-
|
|
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;
|
|
1828
2181
|
}
|
|
1829
2182
|
|
|
1830
2183
|
const existingPost = await this.getById(id);
|
|
@@ -1862,17 +2215,41 @@ export function createPostService(
|
|
|
1862
2215
|
await applyAttachmentAltUpdates(attachments, deps);
|
|
1863
2216
|
|
|
1864
2217
|
const nextAttachmentIds = new Set(orderedMediaIds);
|
|
1865
|
-
const
|
|
1866
|
-
.filter(
|
|
1867
|
-
(attachment) =>
|
|
1868
|
-
attachment.mimeType === "text/x-tiptap+json" &&
|
|
1869
|
-
!nextAttachmentIds.has(attachment.id),
|
|
1870
|
-
)
|
|
2218
|
+
const removedAttachmentIds = existingAttachments
|
|
2219
|
+
.filter((attachment) => !nextAttachmentIds.has(attachment.id))
|
|
1871
2220
|
.map((attachment) => attachment.id);
|
|
1872
2221
|
await deps.media
|
|
1873
|
-
.deleteByIds(
|
|
2222
|
+
.deleteByIds(removedAttachmentIds, deps.storage)
|
|
1874
2223
|
.catch(() => undefined);
|
|
1875
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
|
+
|
|
1876
2253
|
return post;
|
|
1877
2254
|
} catch (error) {
|
|
1878
2255
|
if (replacedAttachments) {
|
|
@@ -1900,17 +2277,16 @@ export function createPostService(
|
|
|
1900
2277
|
const existing = await this.getById(id);
|
|
1901
2278
|
if (!existing) return false;
|
|
1902
2279
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
const thread = await this.getThread(id);
|
|
1908
|
-
postIds = thread.map((p) => p.id);
|
|
1909
|
-
} else {
|
|
1910
|
-
postIds = [id];
|
|
1911
|
-
}
|
|
2280
|
+
const isRoot = !isThreadReply(existing);
|
|
2281
|
+
const affectedPosts: Post[] = isRoot
|
|
2282
|
+
? await this.getThread(id)
|
|
2283
|
+
: [existing];
|
|
1912
2284
|
|
|
1913
|
-
|
|
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
|
+
);
|
|
1914
2290
|
const allMedia = [...mediaMap.values()].flat();
|
|
1915
2291
|
if (allMedia.length > 0) {
|
|
1916
2292
|
await deps.media.deleteByIds(
|
|
@@ -1918,18 +2294,20 @@ export function createPostService(
|
|
|
1918
2294
|
deps.storage,
|
|
1919
2295
|
);
|
|
1920
2296
|
}
|
|
2297
|
+
|
|
2298
|
+
for (const p of affectedPosts) {
|
|
2299
|
+
await deletePreviewImage(p.previewImageKey, deps.storage);
|
|
2300
|
+
}
|
|
1921
2301
|
}
|
|
1922
2302
|
|
|
1923
2303
|
const timestamp = now();
|
|
1924
2304
|
|
|
1925
|
-
|
|
1926
|
-
if (!isThreadReply(existing)) {
|
|
2305
|
+
if (isRoot) {
|
|
1927
2306
|
await db
|
|
1928
2307
|
.update(posts)
|
|
1929
2308
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
1930
2309
|
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
1931
2310
|
} else {
|
|
1932
|
-
// Soft-delete the single reply
|
|
1933
2311
|
await db
|
|
1934
2312
|
.update(posts)
|
|
1935
2313
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
@@ -1940,6 +2318,23 @@ export function createPostService(
|
|
|
1940
2318
|
return true;
|
|
1941
2319
|
},
|
|
1942
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
|
+
|
|
1943
2338
|
async getThread(rootId) {
|
|
1944
2339
|
const rows = await db
|
|
1945
2340
|
.select()
|
|
@@ -2029,7 +2424,7 @@ export function createPostService(
|
|
|
2029
2424
|
const rows = await db
|
|
2030
2425
|
.select({
|
|
2031
2426
|
threadId: posts.threadId,
|
|
2032
|
-
count: sql<number>`count(*)`.as("count"),
|
|
2427
|
+
count: sql<number>`CAST(count(*) AS INTEGER)`.as("count"),
|
|
2033
2428
|
})
|
|
2034
2429
|
.from(posts)
|
|
2035
2430
|
.where(
|
|
@@ -2058,10 +2453,10 @@ export function createPostService(
|
|
|
2058
2453
|
id: posts.id,
|
|
2059
2454
|
threadId: posts.threadId,
|
|
2060
2455
|
createdAt: posts.createdAt,
|
|
2061
|
-
previewRank: sql<number>`ROW_NUMBER() OVER (
|
|
2456
|
+
previewRank: sql<number>`CAST(ROW_NUMBER() OVER (
|
|
2062
2457
|
PARTITION BY ${posts.threadId}
|
|
2063
2458
|
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
2064
|
-
)`.as("preview_rank"),
|
|
2459
|
+
) AS INTEGER)`.as("preview_rank"),
|
|
2065
2460
|
})
|
|
2066
2461
|
.from(posts)
|
|
2067
2462
|
.where(
|
|
@@ -2115,14 +2510,17 @@ export function createPostService(
|
|
|
2115
2510
|
.select({
|
|
2116
2511
|
id: posts.id,
|
|
2117
2512
|
threadId: posts.threadId,
|
|
2118
|
-
|
|
2119
|
-
|
|
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 (
|
|
2120
2518
|
PARTITION BY ${posts.threadId}
|
|
2121
2519
|
ORDER BY ${posts.createdAt} DESC, ${posts.id} DESC
|
|
2122
|
-
)`.as("
|
|
2123
|
-
totalReplyCount: sql<number>`COUNT(*) OVER (
|
|
2520
|
+
) AS INTEGER)`.as("latest_reply_rank"),
|
|
2521
|
+
totalReplyCount: sql<number>`CAST(COUNT(*) OVER (
|
|
2124
2522
|
PARTITION BY ${posts.threadId}
|
|
2125
|
-
)`.as("total_reply_count"),
|
|
2523
|
+
) AS INTEGER)`.as("total_reply_count"),
|
|
2126
2524
|
})
|
|
2127
2525
|
.from(posts)
|
|
2128
2526
|
.where(
|
|
@@ -2136,41 +2534,72 @@ export function createPostService(
|
|
|
2136
2534
|
)
|
|
2137
2535
|
.as("ranked_replies");
|
|
2138
2536
|
|
|
2139
|
-
const
|
|
2537
|
+
const contextRows = await db
|
|
2140
2538
|
.select({
|
|
2141
2539
|
threadId: rankedReplies.threadId,
|
|
2142
|
-
|
|
2143
|
-
|
|
2540
|
+
id: rankedReplies.id,
|
|
2541
|
+
firstReplyRank: rankedReplies.firstReplyRank,
|
|
2542
|
+
latestReplyRank: rankedReplies.latestReplyRank,
|
|
2144
2543
|
totalReplyCount: rankedReplies.totalReplyCount,
|
|
2145
2544
|
})
|
|
2146
2545
|
.from(rankedReplies)
|
|
2147
|
-
.where(
|
|
2546
|
+
.where(
|
|
2547
|
+
or(
|
|
2548
|
+
eq(rankedReplies.firstReplyRank, 1),
|
|
2549
|
+
lte(rankedReplies.latestReplyRank, 2),
|
|
2550
|
+
),
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
const hydratedPosts = await hydratePostsById(
|
|
2554
|
+
contextRows.map((row) => row.id),
|
|
2555
|
+
);
|
|
2148
2556
|
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2557
|
+
const contextByThreadId = new Map<
|
|
2558
|
+
string,
|
|
2559
|
+
{
|
|
2560
|
+
secondReplyId: string | null;
|
|
2561
|
+
penultimateReplyId: string | null;
|
|
2562
|
+
latestReplyId: string | null;
|
|
2563
|
+
totalReplyCount: number;
|
|
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
|
+
};
|
|
2151
2573
|
|
|
2152
|
-
if (row.
|
|
2153
|
-
|
|
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;
|
|
2154
2582
|
}
|
|
2155
2583
|
|
|
2156
|
-
|
|
2157
|
-
}
|
|
2158
|
-
const hydratedPosts = await hydratePostsById(relatedPostIds);
|
|
2584
|
+
contextByThreadId.set(row.threadId, existing);
|
|
2585
|
+
}
|
|
2159
2586
|
|
|
2160
2587
|
const result = new Map<string, ThreadTimelineContext>();
|
|
2161
|
-
for (const
|
|
2162
|
-
|
|
2163
|
-
if (!latestReply) continue;
|
|
2588
|
+
for (const [threadId, context] of contextByThreadId) {
|
|
2589
|
+
if (!context.latestReplyId) continue;
|
|
2164
2590
|
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
? (hydratedPosts.get(row.latestReplyToId) ?? null)
|
|
2168
|
-
: null;
|
|
2591
|
+
const latestReply = hydratedPosts.get(context.latestReplyId);
|
|
2592
|
+
if (!latestReply) continue;
|
|
2169
2593
|
|
|
2170
|
-
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,
|
|
2171
2601
|
latestReply,
|
|
2172
|
-
|
|
2173
|
-
totalReplyCount: row.totalReplyCount,
|
|
2602
|
+
totalReplyCount: context.totalReplyCount,
|
|
2174
2603
|
});
|
|
2175
2604
|
}
|
|
2176
2605
|
|
|
@@ -2185,7 +2614,10 @@ export function createPostService(
|
|
|
2185
2614
|
|
|
2186
2615
|
const rows = await db
|
|
2187
2616
|
.select({
|
|
2188
|
-
count:
|
|
2617
|
+
count:
|
|
2618
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2619
|
+
"count",
|
|
2620
|
+
),
|
|
2189
2621
|
})
|
|
2190
2622
|
.from(posts)
|
|
2191
2623
|
.where(and(...conditions));
|
|
@@ -2241,7 +2673,10 @@ export function createPostService(
|
|
|
2241
2673
|
|
|
2242
2674
|
const rows = await db
|
|
2243
2675
|
.select({
|
|
2244
|
-
count:
|
|
2676
|
+
count:
|
|
2677
|
+
sql<number>`CAST(count(distinct ${posts.threadId}) AS INTEGER)`.as(
|
|
2678
|
+
"count",
|
|
2679
|
+
),
|
|
2245
2680
|
})
|
|
2246
2681
|
.from(posts)
|
|
2247
2682
|
.innerJoin(
|
|
@@ -2272,10 +2707,10 @@ export function createPostService(
|
|
|
2272
2707
|
buildCollectionMembershipCondition(collectionIds),
|
|
2273
2708
|
];
|
|
2274
2709
|
const sortOrder = options.sortOrder ?? "newest";
|
|
2275
|
-
const
|
|
2710
|
+
const publishedAt =
|
|
2276
2711
|
sortOrder === "oldest"
|
|
2277
|
-
? sql<number>`MIN(${
|
|
2278
|
-
: sql<number>`MAX(${
|
|
2712
|
+
? sql<number>`MIN(${posts.publishedAt})`.as("published_at")
|
|
2713
|
+
: sql<number>`MAX(${posts.publishedAt})`.as("published_at");
|
|
2279
2714
|
const ratingPresence = sql<number>`MAX(
|
|
2280
2715
|
CASE
|
|
2281
2716
|
WHEN ${posts.rating} IS NULL THEN 0
|
|
@@ -2286,10 +2721,16 @@ export function createPostService(
|
|
|
2286
2721
|
"rating_value",
|
|
2287
2722
|
);
|
|
2288
2723
|
|
|
2724
|
+
const collectionPinnedAt =
|
|
2725
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2726
|
+
"collection_pinned_at",
|
|
2727
|
+
);
|
|
2728
|
+
|
|
2289
2729
|
const baseQuery = db
|
|
2290
2730
|
.select({
|
|
2291
2731
|
threadId: posts.threadId,
|
|
2292
|
-
|
|
2732
|
+
publishedAt,
|
|
2733
|
+
collectionPinnedAt,
|
|
2293
2734
|
ratingPresence,
|
|
2294
2735
|
ratingValue,
|
|
2295
2736
|
})
|
|
@@ -2306,15 +2747,24 @@ export function createPostService(
|
|
|
2306
2747
|
|
|
2307
2748
|
let query =
|
|
2308
2749
|
sortOrder === "oldest"
|
|
2309
|
-
? baseQuery.orderBy(
|
|
2750
|
+
? baseQuery.orderBy(
|
|
2751
|
+
desc(collectionPinnedAt),
|
|
2752
|
+
asc(publishedAt),
|
|
2753
|
+
asc(posts.threadId),
|
|
2754
|
+
)
|
|
2310
2755
|
: sortOrder === "rating_desc"
|
|
2311
2756
|
? baseQuery.orderBy(
|
|
2757
|
+
desc(collectionPinnedAt),
|
|
2312
2758
|
desc(ratingPresence),
|
|
2313
2759
|
desc(ratingValue),
|
|
2314
|
-
desc(
|
|
2760
|
+
desc(publishedAt),
|
|
2315
2761
|
desc(posts.threadId),
|
|
2316
2762
|
)
|
|
2317
|
-
: baseQuery.orderBy(
|
|
2763
|
+
: baseQuery.orderBy(
|
|
2764
|
+
desc(collectionPinnedAt),
|
|
2765
|
+
desc(publishedAt),
|
|
2766
|
+
desc(posts.threadId),
|
|
2767
|
+
);
|
|
2318
2768
|
|
|
2319
2769
|
if (options.limit !== undefined) {
|
|
2320
2770
|
query = query.limit(options.limit) as typeof query;
|
|
@@ -2339,14 +2789,23 @@ export function createPostService(
|
|
|
2339
2789
|
...buildThreadRootPageConditions(options),
|
|
2340
2790
|
buildCollectionMembershipCondition(collectionIds),
|
|
2341
2791
|
];
|
|
2792
|
+
const publishedAt = sql<number>`MAX(${posts.publishedAt})`.as(
|
|
2793
|
+
"published_at",
|
|
2794
|
+
);
|
|
2342
2795
|
const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
|
|
2343
2796
|
"collected_at",
|
|
2344
2797
|
);
|
|
2798
|
+
const collectionPinnedAt =
|
|
2799
|
+
sql<number>`MAX(coalesce(${postCollections.pinnedAt}, -1))`.as(
|
|
2800
|
+
"collection_pinned_at",
|
|
2801
|
+
);
|
|
2345
2802
|
|
|
2346
2803
|
let query = db
|
|
2347
2804
|
.select({
|
|
2348
2805
|
threadId: posts.threadId,
|
|
2806
|
+
publishedAt,
|
|
2349
2807
|
collectedAt,
|
|
2808
|
+
collectionPinnedAt,
|
|
2350
2809
|
})
|
|
2351
2810
|
.from(posts)
|
|
2352
2811
|
.innerJoin(
|
|
@@ -2358,7 +2817,11 @@ export function createPostService(
|
|
|
2358
2817
|
)
|
|
2359
2818
|
.where(and(...conditions))
|
|
2360
2819
|
.groupBy(posts.threadId)
|
|
2361
|
-
.orderBy(
|
|
2820
|
+
.orderBy(
|
|
2821
|
+
desc(collectionPinnedAt),
|
|
2822
|
+
desc(publishedAt),
|
|
2823
|
+
desc(posts.threadId),
|
|
2824
|
+
);
|
|
2362
2825
|
|
|
2363
2826
|
if (options.limit !== undefined) {
|
|
2364
2827
|
query = query.limit(options.limit) as typeof query;
|