@jant/core 0.3.38 → 0.3.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +189 -189
- package/README.md +6 -4
- package/bin/commands/assets/prepare.js +55 -0
- package/bin/commands/db/execute-file.js +113 -0
- package/bin/commands/db/export.js +5 -0
- package/bin/commands/db/rehearse.js +65 -0
- package/bin/commands/deploy.js +162 -0
- package/bin/commands/export.js +73 -75
- package/bin/commands/import-site.js +1531 -267
- package/bin/commands/migrate.js +79 -0
- package/bin/commands/reset-password.js +80 -7
- package/bin/commands/site/export.js +415 -0
- package/bin/commands/site/import.js +5 -0
- package/bin/commands/site/localize-media.js +118 -0
- package/bin/commands/site/snapshot/export.js +357 -0
- package/bin/commands/site/snapshot/import.js +363 -0
- package/bin/commands/start.js +46 -0
- package/bin/commands/uploads/cleanup.js +119 -0
- package/bin/jant.js +55 -15
- package/bin/lib/cli-api-token.js +6 -0
- package/bin/lib/d1-query.js +194 -0
- package/bin/lib/load-node-runtime.js +10 -0
- package/bin/lib/migration-artifacts.js +99 -0
- package/bin/lib/migration-rehearsal.js +438 -0
- package/bin/lib/migration-runner.js +311 -0
- package/bin/lib/node-database.js +48 -0
- package/bin/lib/node-sqlite.js +158 -0
- package/bin/lib/public-assets.js +66 -0
- package/bin/lib/r2-query.js +170 -0
- package/bin/lib/runtime-target.js +33 -0
- package/bin/lib/site-localize-media.js +427 -0
- package/bin/lib/site-media-parser.js +1150 -0
- package/bin/lib/site-selection.js +222 -0
- package/bin/lib/site-snapshot.js +484 -0
- package/bin/lib/site-url.js +84 -0
- package/bin/lib/sql-export.js +194 -0
- package/bin/lib/wrangler-cli.js +41 -0
- package/bin/lib/wrangler-config.js +86 -0
- package/dist/app-CAtsuLLh.js +30644 -0
- package/dist/client/_assets/chunks/0041f681602cc834bb5c55ced0155b8e-BNpHLJgt.woff2 +0 -0
- package/dist/client/_assets/chunks/008ea9091e332c639ceb18874eacd60c-CygB704Q.woff2 +0 -0
- package/dist/client/_assets/chunks/00c9ac960d866ffaf8a866e5939024e2-CqRrlxSi.woff2 +0 -0
- package/dist/client/_assets/chunks/02629e5d0a9860b7fe32ec1f0563213a-YvgSIv_V.woff2 +0 -0
- package/dist/client/_assets/chunks/02e48e353415a00e0f556608cab33a43-YFgu4zi3.woff2 +0 -0
- package/dist/client/_assets/chunks/02faf6bb0ab4d56ada037c0bbaf9b9f7-DUuUM2eo.woff2 +0 -0
- package/dist/client/_assets/chunks/031089da45fbfb7dc18ac827bef4c56e-D4ahCOTO.woff2 +0 -0
- package/dist/client/_assets/chunks/03ac785139320b7b13bac9c150bf72bf-BG7zubxs.woff2 +0 -0
- package/dist/client/_assets/chunks/053bd3d7aec0040d0cc50c261a1f4e3e-BdG4yI0n.woff2 +0 -0
- package/dist/client/_assets/chunks/057a3d44d7fc606f113d863376d0ecf0-BsEMbuKV.woff2 +0 -0
- package/dist/client/_assets/chunks/0713613227cc4c686c45a279f8bdc166-BzLoJcLo.woff2 +0 -0
- package/dist/client/_assets/chunks/0b1f83a3c7e715560a55ad9eb0fb1c94-asGrAhxW.woff2 +0 -0
- package/dist/client/_assets/chunks/0c055db157e7a13f3103cc2a6b67fec3-1T6huY_I.woff2 +0 -0
- package/dist/client/_assets/chunks/0d3f5cc265cb6c439c517f2c4cebbddf-CmkfpVHX.woff2 +0 -0
- package/dist/client/_assets/chunks/0e97f44ebc65384c346fe19bcc52fa20-BJTzmZzt.woff2 +0 -0
- package/dist/client/_assets/chunks/10f2b44b3711d3f5bdcc30d373b543d1-BRTxD_6-.woff2 +0 -0
- package/dist/client/_assets/chunks/1139d32ae2bdeb26c0c8f31330aa9a9f-B7k4Da0E.woff2 +0 -0
- package/dist/client/_assets/chunks/1259e5825b314fe2b8bb96d6e8069ee5-Ba8VoZCg.woff2 +0 -0
- package/dist/client/_assets/chunks/12c518ebfe62818af550c08947e359e7-B7eDp07N.woff2 +0 -0
- package/dist/client/_assets/chunks/145831a59caa06d894022fe60212ed21-B3xSIk4b.woff2 +0 -0
- package/dist/client/_assets/chunks/14b040a2dda256936bc7b3470e548394-CF7zgNWs.woff2 +0 -0
- package/dist/client/_assets/chunks/14c1506106d92621bafb11016735194e-cbk_paoP.woff2 +0 -0
- package/dist/client/_assets/chunks/154a2c266902003bd8b7449386b10776-CYgd0apt.woff2 +0 -0
- package/dist/client/_assets/chunks/15f8c0df47fd639d1b0d9bd5cf507c9b-B11bYwtn.woff2 +0 -0
- package/dist/client/_assets/chunks/1668bd859ffe15bed7d5563117d8d5fb-DJotYU-j.woff2 +0 -0
- package/dist/client/_assets/chunks/169a096e61d38a773216f51d1ec2cc06-CzS4CZjy.woff2 +0 -0
- package/dist/client/_assets/chunks/1884a2b22d314c7d57707f03aec348e0-BdBJ4_JO.woff2 +0 -0
- package/dist/client/_assets/chunks/189f272ea2600c74d576b7b15c014922-CylwUmhO.woff2 +0 -0
- package/dist/client/_assets/chunks/190e3f8632494e7c095117f26b1c811e-DT09KhZe.woff2 +0 -0
- package/dist/client/_assets/chunks/19ad151c22ce1befe0a9ea643fbee570-BOjbPzxL.woff2 +0 -0
- package/dist/client/_assets/chunks/19e39850472250bfdbce654d30859879-Db8MVQ-5.woff2 +0 -0
- package/dist/client/_assets/chunks/1b5d0d740450fb996749464c9b882025-BG0yepI2.woff2 +0 -0
- package/dist/client/_assets/chunks/1bca0a2a8840ad0ee9414940593db144-CfiRZ0Fs.woff2 +0 -0
- package/dist/client/_assets/chunks/1c820b5295868008ca7c78afa5b7655d-rGkaVY0Z.woff2 +0 -0
- package/dist/client/_assets/chunks/1cda27dcaab977ae4ef5d5ab2a10ae03-3n1CbajX.woff2 +0 -0
- package/dist/client/_assets/chunks/1e2640116bbba817f43c43cc69371cf1-BLnekPNy.woff2 +0 -0
- package/dist/client/_assets/chunks/1f4bc38a1c50f55f335f5411cae47696-3J1JFXeu.woff2 +0 -0
- package/dist/client/_assets/chunks/1fbccc182322b513f57cd156a9a491b0-bRVH28a5.woff2 +0 -0
- package/dist/client/_assets/chunks/1fbe225742c69f4ba9ea5f74922f0ca1-BHRIiKDB.woff2 +0 -0
- package/dist/client/_assets/chunks/2022cf097cb952d9fe75b53b4587d2c3-Btdq5ZEB.woff2 +0 -0
- package/dist/client/_assets/chunks/21e0a71d86be7b8a9e812e7af09dd061-SSZaQU5Z.woff2 +0 -0
- package/dist/client/_assets/chunks/2240a3c43ca5ef59ae3c348c7884792f-DQgT8QLe.woff2 +0 -0
- package/dist/client/_assets/chunks/2573703213da30d3ba18925b100b2c2b-D61uHyrJ.woff2 +0 -0
- package/dist/client/_assets/chunks/265e048cc9f2f8a711ba585a534d5351-geHDzSNf.woff2 +0 -0
- package/dist/client/_assets/chunks/26839c0e47c73514b8d8f660d24d6b19-D2gmzZl-.woff2 +0 -0
- package/dist/client/_assets/chunks/280e3d2b58e9ad3501816072e01b0c13-Dx5yDwjz.woff2 +0 -0
- package/dist/client/_assets/chunks/2874d07e228da9583b0e73646dacd498-26mUURU-.woff2 +0 -0
- package/dist/client/_assets/chunks/29d49891713a2785a3a383001cf58c59-DVb6JpZh.woff2 +0 -0
- package/dist/client/_assets/chunks/2a22e14a9ad53f2abb3c7e85017b7d12-CU_4APNf.woff2 +0 -0
- package/dist/client/_assets/chunks/2a7cedfcd6e4c7cec36f4fd7b0f329c2-BUp7BYdq.woff2 +0 -0
- package/dist/client/_assets/chunks/2acea04a920f6af31e7db97052f563c6-KdL2n7-Y.woff2 +0 -0
- package/dist/client/_assets/chunks/2ae2ca951489c9d50cde5b36a2a5515b-CWV12jwD.woff2 +0 -0
- package/dist/client/_assets/chunks/2b3e8c5703b91f39f6027f43f0da6f4b-FHYDvFqX.woff2 +0 -0
- package/dist/client/_assets/chunks/2ba802b14f21a58fc61606c88fa93373-BmrW6szo.woff2 +0 -0
- package/dist/client/_assets/chunks/2d81eb6ab0ebbc0cabfb3a3341ba8800-Ds2YcMj0.woff2 +0 -0
- package/dist/client/_assets/chunks/2deb444546774c3a3ab38c75eb69cdfb-Dm9USQXk.woff2 +0 -0
- package/dist/client/_assets/chunks/2e98b666924b8e0a09d1aeeefd24bdd2-Cmh3DrKG.woff2 +0 -0
- package/dist/client/_assets/chunks/2f27ee4fb2cf6a280e110e09c18ef73e-D_yYWYMZ.woff2 +0 -0
- package/dist/client/_assets/chunks/2fbccf9a3853eb59db1a825e044515fd-CiShE3Wo.woff2 +0 -0
- package/dist/client/_assets/chunks/2fd3fceb6faed5e3db768e88d7614dca-D74stxc-.woff2 +0 -0
- package/dist/client/_assets/chunks/2ff009fa8701505d7f3dc6c83763f019-nkOuUfA-.woff2 +0 -0
- package/dist/client/_assets/chunks/31342cebfa5ea7fac06b4ea372d96bc5-DdIH_TkU.woff2 +0 -0
- package/dist/client/_assets/chunks/31424fe5d54692e7c8b38021ccb8597c-CfNu45zJ.woff2 +0 -0
- package/dist/client/_assets/chunks/3179006d1c7ebfa50d27482a2859d9a0-e7PNUH9y.woff2 +0 -0
- package/dist/client/_assets/chunks/34c2edb3c37f71258f5c4a31091f0c6c-Cyr8VjPf.woff2 +0 -0
- package/dist/client/_assets/chunks/35cf5dd04315e0b906e1a413d7905a2f-A-QPUrVX.woff2 +0 -0
- package/dist/client/_assets/chunks/360c190344c26278bbc50e2f4d6a2b3f-B7OhzvgR.woff2 +0 -0
- package/dist/client/_assets/chunks/375329ba0b50b94b35006498e555867c-CDc5XB7_.woff2 +0 -0
- package/dist/client/_assets/chunks/387c811226f303af62f1e21aae6f5c83-BmPYUAdb.woff2 +0 -0
- package/dist/client/_assets/chunks/389a950f2a1211946d294716e679e381-Diuxk8JN.woff2 +0 -0
- package/dist/client/_assets/chunks/39b0bbc910af9d2d6dcd8bd4abd6387d-NhvZf71d.woff2 +0 -0
- package/dist/client/_assets/chunks/3adc8f6350cf5067bcb6dc5e44c45d41-DIO3oy0b.woff2 +0 -0
- package/dist/client/_assets/chunks/3b41385fc27419c19822060daa0b5cb3-Dh5JVnsu.woff2 +0 -0
- package/dist/client/_assets/chunks/3bed5bd57de8f738e53cddaea88983d9-BAGVbLtM.woff2 +0 -0
- package/dist/client/_assets/chunks/3c201fd8d1bb20abe7d06b940e83a4d9-Bz0dCLvM.woff2 +0 -0
- package/dist/client/_assets/chunks/3cbe4a697fd595ef42c899de7d3e5445-zWwFRRI_.woff2 +0 -0
- package/dist/client/_assets/chunks/3d385ea0880df7204258e290648ec012-BQqoFH8R.woff2 +0 -0
- package/dist/client/_assets/chunks/3d83dacbbec3d8532ae9afede21f3aab-BH06SEJj.woff2 +0 -0
- package/dist/client/_assets/chunks/3dc1a4f0d7af59e16b5162a2b077a442-CkXZW3CY.woff2 +0 -0
- package/dist/client/_assets/chunks/3dc68e473fe23bd076dd46785cd23583-Cv6HZ0Dr.woff2 +0 -0
- package/dist/client/_assets/chunks/427577dcb707d1d35eebd155b4222aa7-D7-mHA6o.woff2 +0 -0
- package/dist/client/_assets/chunks/42a74b6a625bbf0a9616ed4db3152c88-CQ58KTMS.woff2 +0 -0
- package/dist/client/_assets/chunks/435b7dca567809813fcb395a27ed83a0-C6E3YXBr.woff2 +0 -0
- package/dist/client/_assets/chunks/43693195e775d515689fa035394067fd-DLMrklI6.woff2 +0 -0
- package/dist/client/_assets/chunks/43fb49e5b79ee7e553869d84e6e08b1e-BgOVrd59.woff2 +0 -0
- package/dist/client/_assets/chunks/44dcbbc3cc8f22e613b342d691511ab6-dRyYwGGi.woff2 +0 -0
- package/dist/client/_assets/chunks/450a5b53be0a8a778bb0b623e86b652f-qwII2gXW.woff2 +0 -0
- package/dist/client/_assets/chunks/457485e72835364662dfead6281638c1-Mv279UyL.woff2 +0 -0
- package/dist/client/_assets/chunks/47479c470fae70f10b7c964a7ecbf274-D9VNBclD.woff2 +0 -0
- package/dist/client/_assets/chunks/474fac21b12b7efd71f7c321578878b0-BJRz_Yd3.woff2 +0 -0
- package/dist/client/_assets/chunks/477866c8396474a17317dcac3e7a014f-e90LWIFy.woff2 +0 -0
- package/dist/client/_assets/chunks/478ebdaadda7775c391c5dcab4e697df-RfXJJ1gl.woff2 +0 -0
- package/dist/client/_assets/chunks/488846410760fe128dae939836ca5423-D0YbXQOX.woff2 +0 -0
- package/dist/client/_assets/chunks/48d6a97a185c799be4fe67aaf7edf213-CqLkiJik.woff2 +0 -0
- package/dist/client/_assets/chunks/48eb0a91e50c7f026e248c64145e72af-D8LB2Dye.woff2 +0 -0
- package/dist/client/_assets/chunks/490edb9fc8a4356aea556eed32287464-Bxk-XtyT.woff2 +0 -0
- package/dist/client/_assets/chunks/4a23fe6e82fd496b5eb20401b6164efe-CfhsfLd6.woff2 +0 -0
- package/dist/client/_assets/chunks/4b0e79ba18b2ce424fa93e84996d7cba-B0dS4UvF.woff2 +0 -0
- package/dist/client/_assets/chunks/4bc743968cf1c3ce5711de67ef1ccc4d-Dmn20LlT.woff2 +0 -0
- package/dist/client/_assets/chunks/4c4bdd0b3f3a52e28f3b643c1c5d43be-BLqyuzyK.woff2 +0 -0
- package/dist/client/_assets/chunks/4c96411f3693a9a8657a9c1190f82bce-BhP7AS2j.woff2 +0 -0
- package/dist/client/_assets/chunks/4c9aa12aba2a6a57410eacaff7427916-CLDZgzM7.woff2 +0 -0
- package/dist/client/_assets/chunks/4cca7233bf8ce5dec2e5d146b993d626-Bf7OgJe6.woff2 +0 -0
- package/dist/client/_assets/chunks/4cf0f292f3358bd2f73b1cf4ec1476f3-CIfjw85D.woff2 +0 -0
- package/dist/client/_assets/chunks/4d0a9128d06ea857f203bf5d007b1ab9-BZ-BuqS9.woff2 +0 -0
- package/dist/client/_assets/chunks/4dc0728df0f2ba70796f45f05654c7ba-DGd3NDaT.woff2 +0 -0
- package/dist/client/_assets/chunks/4dc2bc2c55b47f57d13b63aa6b1c8bd4-g3WbclK2.woff2 +0 -0
- package/dist/client/_assets/chunks/4e1cc6aafb411b572c8d3511e925ecf1-BUf15jYj.woff2 +0 -0
- package/dist/client/_assets/chunks/4e5384920bbb155d9d8d74887b09ea5b-2KLteQXT.woff2 +0 -0
- package/dist/client/_assets/chunks/501f66f24bce8234441954de1b568403-CIe4N6pO.woff2 +0 -0
- package/dist/client/_assets/chunks/50cfd672bfa62512ba090420acf35c87-CibXrwCT.woff2 +0 -0
- package/dist/client/_assets/chunks/5227dbe9933760a48baff21ebd13fc98-v8CBITMX.woff2 +0 -0
- package/dist/client/_assets/chunks/526b263e72c189f4b065738aaa6d423a-Dj5Ns6Ev.woff2 +0 -0
- package/dist/client/_assets/chunks/53a88404451448cd2e620a0ca0e45a20-CI0P5NCv.woff2 +0 -0
- package/dist/client/_assets/chunks/54da934819a917f561b439bfd10f88b6-CpoNRhz7.woff2 +0 -0
- package/dist/client/_assets/chunks/54e301f412730f391225db59dae1c8d5-CxTAz9F1.woff2 +0 -0
- package/dist/client/_assets/chunks/551b1d7a0b80c8d42af09863cdca7f01-BIGKH8GS.woff2 +0 -0
- package/dist/client/_assets/chunks/555d990ab3fd7d3d66c6d1fa9a82fec5-BndAe6f2.woff2 +0 -0
- package/dist/client/_assets/chunks/557cd00c5d6827e13d72a0c71b23587b-C6gpr-g-.woff2 +0 -0
- package/dist/client/_assets/chunks/563fa31542d553f25abab65cf7f81e1d-Cl_1me5X.woff2 +0 -0
- package/dist/client/_assets/chunks/56e1c4734bbbb38af2fbc262bf6e98f2-DZ32amUx.woff2 +0 -0
- package/dist/client/_assets/chunks/5947f5da5da9a352a2b534ee64bfc29a-DT0Fj-SL.woff2 +0 -0
- package/dist/client/_assets/chunks/5979c33a7eb5963bf8e83e46931b5fb5--4f9yv1z.woff2 +0 -0
- package/dist/client/_assets/chunks/597d69d0710e0178b162afb0a0c20401-CpThXFdo.woff2 +0 -0
- package/dist/client/_assets/chunks/59966ee0b069b577510fe68c350da0ee-Bs_HDvyP.woff2 +0 -0
- package/dist/client/_assets/chunks/5a10741e41259e235841440394c0763d-uCZGOg0A.woff2 +0 -0
- package/dist/client/_assets/chunks/5bfc7a121c35ae42623ef804fb525e0e-mS7vyFFz.woff2 +0 -0
- package/dist/client/_assets/chunks/5cc23a76e122d0ad2f7cede41bc35b27-4T1ZVQJu.woff2 +0 -0
- package/dist/client/_assets/chunks/5d48855bed5f3554eff91b573d7376ac-CPzsdNDF.woff2 +0 -0
- package/dist/client/_assets/chunks/5ddcbe564b29ef08632e1aeb33455435-CQzDKa-z.woff2 +0 -0
- package/dist/client/_assets/chunks/5f90024544c2907c6c0203c6210c50be-C_1uxIky.woff2 +0 -0
- package/dist/client/_assets/chunks/605667a998e91e2b6a4a3cd7c31fe5a9-Co2lOjER.woff2 +0 -0
- package/dist/client/_assets/chunks/60a14064ed334f0155795d795e926abe-C2HuVyw5.woff2 +0 -0
- package/dist/client/_assets/chunks/60d8b0805a0a8c54a6cca216004beff5-CV1GX5br.woff2 +0 -0
- package/dist/client/_assets/chunks/611b62d5fd9698d9b5ce495ba6f14c93-CPi2xRKF.woff2 +0 -0
- package/dist/client/_assets/chunks/61bf4287453da4025d03fa6b2dba66ca-CMh4XzzB.woff2 +0 -0
- package/dist/client/_assets/chunks/638369541268ed5a10af97ad77498c73-DrpvGu-L.woff2 +0 -0
- package/dist/client/_assets/chunks/649b12d7cee7bb981842946e4547e6ca-EACt8KkC.woff2 +0 -0
- package/dist/client/_assets/chunks/653bef2ed891ae48d8ed712283080649-C3d1ZTAc.woff2 +0 -0
- package/dist/client/_assets/chunks/67d2a81f06ba352f17fbdc3a5e6ea59e-DC8Iostn.woff2 +0 -0
- package/dist/client/_assets/chunks/67f32ceea9e78e5109f87724ad886010-C_3-9sSn.woff2 +0 -0
- package/dist/client/_assets/chunks/68304f3229cf763465f044fccb5892c0-CTvPlN8c.woff2 +0 -0
- package/dist/client/_assets/chunks/687d0f0f90a9b23e40102e16ad8e9836-Bzlx1W-7.woff2 +0 -0
- package/dist/client/_assets/chunks/688a88911e4da17b609196a959b8b930-BgwP4zIQ.woff2 +0 -0
- package/dist/client/_assets/chunks/68f2fab82ec8e9291f08c3145111549c-CXqagiiH.woff2 +0 -0
- package/dist/client/_assets/chunks/69519ada3f3f74ca20aacb8af48ab6b4-DncDOElk.woff2 +0 -0
- package/dist/client/_assets/chunks/6a6e884fb2b65ec5b4a3d5ecd0d01a6a-DBNJo4jq.woff2 +0 -0
- package/dist/client/_assets/chunks/6db6ddf72c38a78ce44c1327701152e1-CFUWMaQQ.woff2 +0 -0
- package/dist/client/_assets/chunks/6e1a8b45b01939088c3a8cfcf8c10681-BemqZSaI.woff2 +0 -0
- package/dist/client/_assets/chunks/6e2164fad867d166de2e5b274f04a563-_YD_QMym.woff2 +0 -0
- package/dist/client/_assets/chunks/6e83fe0b6e708eaf1c3003d6dee11488-CRqY8z2g.woff2 +0 -0
- package/dist/client/_assets/chunks/6eefc9d430171c1e1e4034ecadee31c8-d3VCnQ01.woff2 +0 -0
- package/dist/client/_assets/chunks/70861376e5d4f92f8aa7aa1b2749b617-BqwxmSo2.woff2 +0 -0
- package/dist/client/_assets/chunks/70adaf50c56d5ff859c64d35e0f1e34e-BCmTRExx.woff2 +0 -0
- package/dist/client/_assets/chunks/7124d150570d39ced8d45507dc11ca1e-Co_mUkqP.woff2 +0 -0
- package/dist/client/_assets/chunks/71eafb8fbe3a734283517e230ad8b6db-CFmZ-qAS.woff2 +0 -0
- package/dist/client/_assets/chunks/72ee453ac0e19bd2c631c8921c44e3de-CEidyyfI.woff2 +0 -0
- package/dist/client/_assets/chunks/751f54dbb115140d5b645a6ba4aff5d3-DY-S96zI.woff2 +0 -0
- package/dist/client/_assets/chunks/753b5f6fb254bacb6618ace25af3df60-uBG6c32o.woff2 +0 -0
- package/dist/client/_assets/chunks/7609e7e74dd4d916a7abc7ecc7d95f7e-CzrNhght.woff2 +0 -0
- package/dist/client/_assets/chunks/76b9d6fe838ae4151d95ce7200aa2bf6-BfQWDykU.woff2 +0 -0
- package/dist/client/_assets/chunks/76d4244186d118eea245d1385a4de2ec-CEoql1pw.woff2 +0 -0
- package/dist/client/_assets/chunks/77a7533bd21ccd33192d142a93555aa8-BzSb8ker.woff2 +0 -0
- package/dist/client/_assets/chunks/78ce29fed872e44fc9014d94875d2aac-D1BDuJXp.woff2 +0 -0
- package/dist/client/_assets/chunks/79a7fdf7d9c722b5723ae25e6ff8e203-5rPAnIr7.woff2 +0 -0
- package/dist/client/_assets/chunks/79a85a253e9b3f12d2e2cb15e16b3003-DTjWGv2X.woff2 +0 -0
- package/dist/client/_assets/chunks/7a86b155111ba20f3e87306ff6beac77-DchSNxJ1.woff2 +0 -0
- package/dist/client/_assets/chunks/7b1e76975b0984e6f83e3f9f8069e784-OB_rGzsR.woff2 +0 -0
- package/dist/client/_assets/chunks/7b6c60131822a0e4d36d980d52509d4e-8dQVuUiW.woff2 +0 -0
- package/dist/client/_assets/chunks/7caa14a095a6bc313aab780fe4ff7999-BJ-80Ois.woff2 +0 -0
- package/dist/client/_assets/chunks/7cbda564cb2dd4799ab9e89d51286aa7-MlT7YRSz.woff2 +0 -0
- package/dist/client/_assets/chunks/7d138084cf03c14116b11297fce0e3e3-CdWCcyam.woff2 +0 -0
- package/dist/client/_assets/chunks/7d65a3d6a65050eb5e6eca43398aeba4-CYB359u-.woff2 +0 -0
- package/dist/client/_assets/chunks/7dfc711962c8771f97e7c8898a6bcb65-Yv69yrg4.woff2 +0 -0
- package/dist/client/_assets/chunks/7ef123b62d530fcba73974fa265e0aae-CD7IMlBc.woff2 +0 -0
- package/dist/client/_assets/chunks/7f60eefa15956d6f06dd92404887d58c-LqioDjlc.woff2 +0 -0
- package/dist/client/_assets/chunks/7f8c15e0ecb102738981d9fa4cb6b921-8qiw7Fmn.woff2 +0 -0
- package/dist/client/_assets/chunks/80466082a896fd328f30a78593c7c568-BMAVDyAy.woff2 +0 -0
- package/dist/client/_assets/chunks/812b5a4b87f3a7b4afc1cfebc864f413-B4_mWnLK.woff2 +0 -0
- package/dist/client/_assets/chunks/812dfb7f8144d01b3cc9d5ce0b472f40-DxhfKDhh.woff2 +0 -0
- package/dist/client/_assets/chunks/84742b1ede4f0bb6d27131298eba21b4-4_QYQ-wj.woff2 +0 -0
- package/dist/client/_assets/chunks/8555f0285e3d28e95e2fc0ccccd9caff-CEnfoaRy.woff2 +0 -0
- package/dist/client/_assets/chunks/880162ae92cd9e120eb4e4e11fae459d-Cj61LKOn.woff2 +0 -0
- package/dist/client/_assets/chunks/8a3c84b0df36f851f5fea75ee8757951-CfHrA-x8.woff2 +0 -0
- package/dist/client/_assets/chunks/8b0c8c9f8cfa9fa090d97c5a5efb1f4c-DZc3EGF4.woff2 +0 -0
- package/dist/client/_assets/chunks/8c8393bc875f1ee36697a2113f4421ea-CnBugeCf.woff2 +0 -0
- package/dist/client/_assets/chunks/8dc035a34c76e6515ca203e2df182588-B1reQPCB.woff2 +0 -0
- package/dist/client/_assets/chunks/8e04e64c8f68d292a18d4160fbde8671-DkT7v-sd.woff2 +0 -0
- package/dist/client/_assets/chunks/8e6c9bb43afb8cbbff7cf1055e67c9bd-c7wzPI7v.woff2 +0 -0
- package/dist/client/_assets/chunks/8eb06109812cb80be44f47b8179c2709-DOaU0xD8.woff2 +0 -0
- package/dist/client/_assets/chunks/8f2b960c2823670e94f5b08aa65657e6-CHT-Gunr.woff2 +0 -0
- package/dist/client/_assets/chunks/8f89f57230d184f92a36e241874229d7-D6P0FMCn.woff2 +0 -0
- package/dist/client/_assets/chunks/904324af375d5fd370af1054355a050e-CHiXmgO7.woff2 +0 -0
- package/dist/client/_assets/chunks/90ac4f9d2aa02afdace2843b49fc18bb-CWArmWzF.woff2 +0 -0
- package/dist/client/_assets/chunks/90b6f57d77847f512fd11db74fa912f1-DzYbq8hW.woff2 +0 -0
- package/dist/client/_assets/chunks/911a2092d64d6d6494b254d819af2b91-C0xmZ-Cu.woff2 +0 -0
- package/dist/client/_assets/chunks/913759e6690f9fc0746a20b96f4bdcb4-B0k9hqx0.woff2 +0 -0
- package/dist/client/_assets/chunks/9154e26efe532a85a27d80902f5a2d6c-DJQmzPmB.woff2 +0 -0
- package/dist/client/_assets/chunks/9180de34b48b325200a97e267befff32-ClJx20hD.woff2 +0 -0
- package/dist/client/_assets/chunks/94e7ed67f1557b76fead6b6e456a0415-CjAhhvRn.woff2 +0 -0
- package/dist/client/_assets/chunks/95127a92346c04fec1fa81d6295b0a28-DNbeXPGm.woff2 +0 -0
- package/dist/client/_assets/chunks/958efb9b2fa2ea0008ffef009885f9f8-CNOwBDS1.woff2 +0 -0
- package/dist/client/_assets/chunks/95df3b9f681d9df411c30aea5b24f2e0-Dv7GHFVj.woff2 +0 -0
- package/dist/client/_assets/chunks/975af5a496e8d87d821910aa9fe4d598-CG5KDMOI.woff2 +0 -0
- package/dist/client/_assets/chunks/97a874bbf55ce89a4ab7cd27c7e938b1-fhost-bL.woff2 +0 -0
- package/dist/client/_assets/chunks/9ca9b71010a5faeee7047ef97aeee13b-K6NP7P3o.woff2 +0 -0
- package/dist/client/_assets/chunks/9cd0b77920b9d6c64eb686493123fc76-Db6wX5BE.woff2 +0 -0
- package/dist/client/_assets/chunks/9de02d745b8e25c6411fb152fb067748-CGxZtq8G.woff2 +0 -0
- package/dist/client/_assets/chunks/9eb33a430058d839ebbe2af4b2e0daa9-Cjd-WSds.woff2 +0 -0
- package/dist/client/_assets/chunks/9ebd27835ffcbd794e67151ab046ce68-DmxtZVr5.woff2 +0 -0
- package/dist/client/_assets/chunks/9f5a73aa8ba417688019d628f334db07-P03uxox_.woff2 +0 -0
- package/dist/client/_assets/chunks/9fbc06b2e3ff16b9d705c76db563ef17-XDHSv5Te.woff2 +0 -0
- package/dist/client/_assets/chunks/9fd53607094e329fa8e5c785b3ff0f1a-B-9qgOdM.woff2 +0 -0
- package/dist/client/_assets/chunks/a077f51cfb5cffb4ff4d8e229c0e9e79-BaPgyZ5H.woff2 +0 -0
- package/dist/client/_assets/chunks/a0f0c06d5c7a3ffa97706178cce212a8-DN_NE4Ic.woff2 +0 -0
- package/dist/client/_assets/chunks/a38c1830367f784181b6f544b0b11bbd-BmPGDXn1.woff2 +0 -0
- package/dist/client/_assets/chunks/a397997b579d3945c9c70a979c17a8ad-DXa54wGz.woff2 +0 -0
- package/dist/client/_assets/chunks/a3b929542e6c5a0644b73a7c8a8b6c03-B5i2p-Hj.woff2 +0 -0
- package/dist/client/_assets/chunks/a578742770fcd2226e3c45b5b6efdcb0-C1QhQdHg.woff2 +0 -0
- package/dist/client/_assets/chunks/a68d9d5027803832bb28e78cdcd04949-D_bwWhwg.woff2 +0 -0
- package/dist/client/_assets/chunks/a8857f5d478f101c053ba02d2f223e90-CHOaRPpq.woff2 +0 -0
- package/dist/client/_assets/chunks/a904b05966368bcf90b632c7c2e5f76b-BbTXYQVh.woff2 +0 -0
- package/dist/client/_assets/chunks/a9cf85e27428c14351d30eac8cbc8d91-DMaVc3-8.woff2 +0 -0
- package/dist/client/_assets/chunks/aa0ce6740f301351761a0615cc8b2e99-4RL3pitV.woff2 +0 -0
- package/dist/client/_assets/chunks/aa218a2c45f3749537ce876201e5152b-DndHqA-p.woff2 +0 -0
- package/dist/client/_assets/chunks/aa28db16818f9eaa8c817f289e1c3270-Drm1Jwhe.woff2 +0 -0
- package/dist/client/_assets/chunks/aa64c9953af43ca65832f413895bb433-CR32RteD.woff2 +0 -0
- package/dist/client/_assets/chunks/aa96d698491c2540e2dcf7009c65c456-BXlPVR0v.woff2 +0 -0
- package/dist/client/_assets/chunks/ada8f0241244c60ec8d3d59ad37f20a5-BFQsznEm.woff2 +0 -0
- package/dist/client/_assets/chunks/ae25c41034ddc1a9e0b41f5034c9aa4b-COQJkHOc.woff2 +0 -0
- package/dist/client/_assets/chunks/ae289ae3f8cdb54a3a6c07174517afec-DFGIoTY6.woff2 +0 -0
- package/dist/client/_assets/chunks/ae401fb4db80d5ff5cd3f8d9bc811070-Byt9AwoT.woff2 +0 -0
- package/dist/client/_assets/chunks/b02dfa2aa52cbdb1b2f11a9f44335469-DVWsuPSL.woff2 +0 -0
- package/dist/client/_assets/chunks/b0ab3a7f319ce6dd3c9a4de2674e7c72-CksmA6Hr.woff2 +0 -0
- package/dist/client/_assets/chunks/b159deb135e9946eea0572d52778170b-BZrtOTn0.woff2 +0 -0
- package/dist/client/_assets/chunks/b2e326f7f9b807451bf9c745df747efe-BvlONCPB.woff2 +0 -0
- package/dist/client/_assets/chunks/b341de0bc0bfe194a6c28dcfb566029e-DJAj3r6U.woff2 +0 -0
- package/dist/client/_assets/chunks/b846c293981ca5429eabaa967f222f26-GS-TAaO6.woff2 +0 -0
- package/dist/client/_assets/chunks/b91450304d9ac44f5c6e0da0792e055d-vS0S7kl0.woff2 +0 -0
- package/dist/client/_assets/chunks/baa325551b381c5e035ef143e56d4abe-DJg3GhKJ.woff2 +0 -0
- package/dist/client/_assets/chunks/bc3f0cb8b55ee11d32b94ca488976f8d-Dp5KZMSD.woff2 +0 -0
- package/dist/client/_assets/chunks/bcb3307527d6d0033bf0f17660b91e71-BMgRW_eW.woff2 +0 -0
- package/dist/client/_assets/chunks/be64f9379412876e00fd3a0bfa6b6fe9-BJ3FMWFM.woff2 +0 -0
- package/dist/client/_assets/chunks/befed8a4fa817773fa7109db6fe07f56-3sAmDsOO.woff2 +0 -0
- package/dist/client/_assets/chunks/bf1acc86e17b4229c548828a9d6f455d-JNuPCGDM.woff2 +0 -0
- package/dist/client/_assets/chunks/c09ee2b219982f8d46ad9968b7e6e0ba-qVb8yJHb.woff2 +0 -0
- package/dist/client/_assets/chunks/c0c7836749e585cee24ab2f8457c5b01-Ds0003oT.woff2 +0 -0
- package/dist/client/_assets/chunks/c2ac4ef1860812036ca2b8c4e4089bdc-DsziO8Du.woff2 +0 -0
- package/dist/client/_assets/chunks/c31019c08bd22464f7a88f090281404c-CUC0jMW1.woff2 +0 -0
- package/dist/client/_assets/chunks/c33c59feccf391f0c5f1f5d24e36d1fe-BXTVggad.woff2 +0 -0
- package/dist/client/_assets/chunks/c39ec937c6a8d124e8b68cf829ea5ad4-CarrkdmQ.woff2 +0 -0
- package/dist/client/_assets/chunks/c3fbc1f2557c343863a10698f8c966a2-BiOC1yo2.woff2 +0 -0
- package/dist/client/_assets/chunks/c3fd21315345ae541f6e98067059fa19-rHtO7vK5.woff2 +0 -0
- package/dist/client/_assets/chunks/c568a16e3168ceb1f191b70022c492ea-BpAiyU80.woff2 +0 -0
- package/dist/client/_assets/chunks/c57ee3b49b7e45b995539a6b2c51f138-IHDXeVRU.woff2 +0 -0
- package/dist/client/_assets/chunks/c5c1c0be944ea39a3f50a02d32f5b759-DAwbrvTL.woff2 +0 -0
- package/dist/client/_assets/chunks/c5e66d60be3375835bbd8d6b797f6eac-DWp4tpvN.woff2 +0 -0
- package/dist/client/_assets/chunks/c5f1075caf6d1344ee720de85114a521-DTTbeLhd.woff2 +0 -0
- package/dist/client/_assets/chunks/c82fd9456d7465b5e5bd3659e9b14c55-Cs6h6hE6.woff2 +0 -0
- package/dist/client/_assets/chunks/c90b7b65d2b9696fbf3a506738f94d68-DsvgiuQQ.woff2 +0 -0
- package/dist/client/_assets/chunks/cae29b3f8951eaf20d2f61c2206e28d9-CoHXmhmG.woff2 +0 -0
- package/dist/client/_assets/chunks/cba6ad3981cb7861428d4be169ee8124-CBdFsPAF.woff2 +0 -0
- package/dist/client/_assets/chunks/cc26525aa2af1f0b929af32ce50a7fba-D55LVtW9.woff2 +0 -0
- package/dist/client/_assets/chunks/cd0e7b51eddb22a77a09b025c0281434-ByI_TSTq.woff2 +0 -0
- package/dist/client/_assets/chunks/cd10a3af2133805d8c92104d1ee6ff18-Dtr5mBXe.woff2 +0 -0
- package/dist/client/_assets/chunks/cd6d074f3957d58bac58437fc97e5e33-CrVZuSbw.woff2 +0 -0
- package/dist/client/_assets/chunks/cd75ca47da9ae4c0899e37d4c543319b-DXU1s4Ja.woff2 +0 -0
- package/dist/client/_assets/chunks/cef249b6d013fb0cc0d574176bc23811-BL7WfNHA.woff2 +0 -0
- package/dist/client/_assets/chunks/d043b8d7a48bb0ac59ee1f1477d88eee-d5CwY2I8.woff2 +0 -0
- package/dist/client/_assets/chunks/d0bd387fda28e58d3c9b3efa2468dd8a-CjDFowFS.woff2 +0 -0
- package/dist/client/_assets/chunks/d12ce1d8445213317f9163283e58a05d-4RtGQnak.woff2 +0 -0
- package/dist/client/_assets/chunks/d15a3317942b7d31978a759fbf2222c8-DQ9QFQdS.woff2 +0 -0
- package/dist/client/_assets/chunks/d28fb13acf9ced9f0657fd4012c81cd2-D8Bgk6sd.woff2 +0 -0
- package/dist/client/_assets/chunks/d320b000b5978c7251148a6a154741b8-CcGZjeFx.woff2 +0 -0
- package/dist/client/_assets/chunks/d3714e6b90de8e2085dfb2514464dd6a-8LsVAcmb.woff2 +0 -0
- package/dist/client/_assets/chunks/d3beff96216c8af1aa79246476b6a323-hQysoMK3.woff2 +0 -0
- package/dist/client/_assets/chunks/d3e311f30c811dc339c262a79a51877e-CkUTbS74.woff2 +0 -0
- package/dist/client/_assets/chunks/d51f4cdc83711e510f5d25f03235597e-BY3VAGvr.woff2 +0 -0
- package/dist/client/_assets/chunks/d5df4a8dfd4328c67d933b3912c6ad0f-A2fccswC.woff2 +0 -0
- package/dist/client/_assets/chunks/d740dc2e854aaa7b3dcdd3ed25455eeb-Cw8LrB06.woff2 +0 -0
- package/dist/client/_assets/chunks/d8325ba7ae651bc30440905bd67b95f1-Dg8M_ijr.woff2 +0 -0
- package/dist/client/_assets/chunks/da13b136efb1d1e4c76575af8f79a698-BbfzLGVB.woff2 +0 -0
- package/dist/client/_assets/chunks/da2cf0ec56bf69374ee37764c7e3ea3d-fyVBKE2g.woff2 +0 -0
- package/dist/client/_assets/chunks/da93ae099ff3b7aae27b3f674d3fc721-BDG5ywtd.woff2 +0 -0
- package/dist/client/_assets/chunks/daf62255dd60679946f28c442ca62533-BnIjYSn3.woff2 +0 -0
- package/dist/client/_assets/chunks/dccda6a2e2db3b530788bdfa2acd1979-ByL7eZcn.woff2 +0 -0
- package/dist/client/_assets/chunks/dd01a1035345f6921a48525b8ce08f06-vkP6t4fC.woff2 +0 -0
- package/dist/client/_assets/chunks/e2204cf85edcb96c5de5c3dcf240da9d-Ch3zKjD-.woff2 +0 -0
- package/dist/client/_assets/chunks/e264213b9e102dabc603adb6e4fda5e6-CyxmQBHB.woff2 +0 -0
- package/dist/client/_assets/chunks/e3e913e145ddcd9323b2a0972967feb6-3sjEdzck.woff2 +0 -0
- package/dist/client/_assets/chunks/e4fb59479cedc87ba79785590bf861ca-sx7RZ8Vu.woff2 +0 -0
- package/dist/client/_assets/chunks/e5d00355f73293d40b61299459d17ca5-D8Vt3D-z.woff2 +0 -0
- package/dist/client/_assets/chunks/e647b8d2efc501c0cc0e407249cc7135-BrSW9DU4.woff2 +0 -0
- package/dist/client/_assets/chunks/e6e60ffb2ebd1828628764b507060aea-Dqyf48QC.woff2 +0 -0
- package/dist/client/_assets/chunks/e7c7ef3669ae48c0a736f06ca471e1d7-DNCebEB-.woff2 +0 -0
- package/dist/client/_assets/chunks/e81a742cacef744130c40de1b90837d8-DtAIFBk_.woff2 +0 -0
- package/dist/client/_assets/chunks/e8b755172122d1d0a5dd453e96b0ff24-BNTSyPYU.woff2 +0 -0
- package/dist/client/_assets/chunks/e99280299c305402eaa5271a3e36c49b-BpqXwl7U.woff2 +0 -0
- package/dist/client/_assets/chunks/e9c66b085052ece66bfadf45f711d3e1-DVza3Uob.woff2 +0 -0
- package/dist/client/_assets/chunks/ed7c6dafaa6d8bcf015ef0ca574837df-C7bcndJl.woff2 +0 -0
- package/dist/client/_assets/chunks/f00eb499abb94fa7b799d6d8c9b050e9-D_XvVTI9.woff2 +0 -0
- package/dist/client/_assets/chunks/f2900a1d30c3a33129f4e2225669bd0e-in84LilX.woff2 +0 -0
- package/dist/client/_assets/chunks/f2fb1f1fbf7e44afb53c672ec286a22e-tFk0sTQc.woff2 +0 -0
- package/dist/client/_assets/chunks/f372129c60aaece937cf7b91ee75c9b8-DiP_JYOt.woff2 +0 -0
- package/dist/client/_assets/chunks/f5237486197aeff59341a1ff38b8eff8-D_uuqrNZ.woff2 +0 -0
- package/dist/client/_assets/chunks/f553d54ef931066712d8f3f0ce018e1b-D8K983U5.woff2 +0 -0
- package/dist/client/_assets/chunks/f5d7487963d43c89da63aaf10f2e6fb7-B7GIrfPU.woff2 +0 -0
- package/dist/client/_assets/chunks/f649cba8e14c33d6bf2265483b14b895-Nm5wSov8.woff2 +0 -0
- package/dist/client/_assets/chunks/f6839df1bf7cb4dc8d27e5ea55bbe633-BIjWKN84.woff2 +0 -0
- package/dist/client/_assets/chunks/f6b7304e028980f77a7f7007bb540abd-CIlBt6sf.woff2 +0 -0
- package/dist/client/_assets/chunks/f75496953a40ff241178240209f56990-MOSKd67d.woff2 +0 -0
- package/dist/client/_assets/chunks/f7d36ffff7a75c9c6216d576a57dd00d-CaXQA5A8.woff2 +0 -0
- package/dist/client/_assets/chunks/f7f3f63e7a149cd89eccab3b52171d05-oNOOSdDV.woff2 +0 -0
- package/dist/client/_assets/chunks/f8db8bef0a6e1178835d350ae0d384a1-ZAiKM369.woff2 +0 -0
- package/dist/client/_assets/chunks/f92d74d1d217d21b39075ff23f79f7fd-BlTe0_IX.woff2 +0 -0
- package/dist/client/_assets/chunks/f9d6d981d8b87b3e469027277f585741-DQqIv77a.woff2 +0 -0
- package/dist/client/_assets/chunks/fa7d3b99744d7f2dc9e00864a97a62d6-Pfww07DK.woff2 +0 -0
- package/dist/client/_assets/chunks/fa8ed469ef290bfeb571418fe0abb628-D5v-Z7kY.woff2 +0 -0
- package/dist/client/_assets/chunks/fb0e90665980954719c2eb685b130bc0-CiWRvo58.woff2 +0 -0
- package/dist/client/_assets/chunks/fb4649a82c50620773d79820e2e5ff13-dw14czgx.woff2 +0 -0
- package/dist/client/_assets/chunks/fb61b690208eff56e6d8432951270901-CUAnVvWa.woff2 +0 -0
- package/dist/client/_assets/chunks/fb9402d6c6357a825affc402f14d5a7e-CIp4G53L.woff2 +0 -0
- package/dist/client/_assets/chunks/fcc41f6a067ddd658bba5c9dff234a32-BtZTNo2r.woff2 +0 -0
- package/dist/client/_assets/chunks/fd6ad889fcf3583bd9b0b6db53aad434-DqiKyYmz.woff2 +0 -0
- package/dist/client/_assets/chunks/ff0937ad63cda71ff420945ead55ab4d-CXGPdnOz.woff2 +0 -0
- package/dist/client/_assets/chunks/heic-to-XcUDQvtx.js +1 -0
- package/dist/client/_assets/chunks/literata-cyrillic-ext-wght-normal-CGKlZYBf.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-cyrillic-wght-normal-DLqwHbi6.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-greek-ext-wght-normal-e3e57Shi.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-greek-wght-normal-CO1l-giJ.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-latin-ext-wght-normal-BnEbWgdZ.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-latin-wght-normal-DLxlUchJ.woff2 +0 -0
- package/dist/client/_assets/chunks/literata-vietnamese-wght-normal-LcSrhZ7T.woff2 +0 -0
- package/dist/client/_assets/chunks/module-ChVQstFd.js +716 -0
- package/dist/client/_assets/chunks/native-CR5HLOyf.js +1 -0
- package/dist/client/_assets/chunks/news-cycle-cyrillic-400-normal-9BSXki1I.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-cyrillic-400-normal-CUYmhJri.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-cyrillic-ext-400-normal-CKyb9yaQ.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-cyrillic-ext-400-normal-Llxpm7uO.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-greek-400-normal-BiAZ91Zl.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-greek-400-normal-D88oNngQ.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-greek-ext-400-normal-Ckclqjrc.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-greek-ext-400-normal-oz-bBDWj.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-latin-400-normal-BLgpQ3uo.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-latin-400-normal-BgW97ttO.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-latin-ext-400-normal-DmDltzLi.woff2 +0 -0
- package/dist/client/_assets/chunks/news-cycle-latin-ext-400-normal-lewDoXxP.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-vietnamese-400-normal-QPSuG-pc.woff +0 -0
- package/dist/client/_assets/chunks/news-cycle-vietnamese-400-normal-t2mxI9x9.woff2 +0 -0
- package/dist/client/_assets/chunks/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
- package/dist/client/_assets/chunks/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
- package/dist/client/_assets/chunks/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
- package/dist/client/_assets/chunks/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
- package/dist/client/_assets/chunks/url-CG0eolsk.js +1 -0
- package/dist/client/_assets/client-auth.js +3260 -0
- package/dist/client/_assets/client-cjk-tc.css +1 -0
- package/dist/client/_assets/client-cjk.css +1 -0
- package/dist/client/_assets/client.css +2 -0
- package/dist/client/_assets/client.js +380 -0
- package/dist/index.js +2 -20359
- package/dist/node.js +479 -0
- package/package.json +67 -40
- package/src/__tests__/bin/uploads-cleanup.test.ts +77 -0
- package/src/__tests__/export-service.test.ts +550 -0
- package/src/__tests__/helpers/app.ts +60 -27
- package/src/__tests__/helpers/db.ts +14 -24
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +15 -2
- package/src/__tests__/import-site-command.test.ts +406 -0
- package/src/__tests__/site-localize-media.test.ts +150 -0
- package/src/__tests__/site-media-parser.test.ts +216 -0
- package/src/app.tsx +277 -175
- package/src/assets/branding/generated/README.txt +15 -0
- package/src/assets/branding/generated/jant-apple-touch-icon.png +0 -0
- package/src/assets/branding/generated/jant-brand-assets.zip +0 -0
- package/src/assets/branding/generated/jant-brand-tile-512.png +0 -0
- package/src/assets/branding/generated/jant-brand-tile.svg +1 -0
- package/src/assets/branding/generated/jant-circle-tile-512.png +0 -0
- package/src/assets/branding/generated/jant-circle-tile.svg +1 -0
- package/src/assets/branding/generated/jant-favicon.ico +0 -0
- package/src/assets/branding/generated/jant-logo-negative.svg +1 -0
- package/src/assets/branding/generated/jant-logo-positive-512.png +0 -0
- package/src/assets/branding/generated/jant-logo-positive.svg +1 -0
- package/src/assets/branding/generated/jant-social-preview.png +0 -0
- package/src/assets/branding/generated/jant-square-tile-512.png +0 -0
- package/src/assets/branding/generated/jant-square-tile.svg +1 -0
- package/src/assets/branding/jant-logo-positive.svg +3 -0
- package/src/auth.ts +47 -6
- package/src/client/__tests__/avatar-upload.test.ts +186 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +127 -0
- package/src/client/__tests__/collection-page-actions.test.ts +112 -0
- package/src/client/__tests__/collection-sort-menu.test.ts +78 -0
- package/src/client/__tests__/compose-bridge.test.ts +260 -0
- package/src/client/__tests__/compose-discovery.test.ts +155 -0
- package/src/client/__tests__/compose-shortcuts.test.ts +133 -0
- package/src/client/__tests__/confirm.test.ts +98 -0
- package/src/client/__tests__/custom-url-menu.test.ts +104 -0
- package/src/client/__tests__/form-enter-submit.test.ts +110 -0
- package/src/client/__tests__/post-form-bridge.test.ts +82 -0
- package/src/client/__tests__/sortable-list.test.ts +91 -0
- package/src/client/__tests__/toast.test.ts +46 -0
- package/src/client/archive-nav.js +1 -3
- package/src/client/avatar-upload.ts +66 -25
- package/src/client/collection-form-bridge.ts +32 -15
- package/src/client/collection-page-actions.ts +160 -0
- package/src/client/collection-sort-menu.ts +105 -0
- package/src/client/components/__tests__/jant-collection-form.test.ts +199 -51
- package/src/client/components/__tests__/jant-collection-sidebar.test.ts +306 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +2507 -342
- package/src/client/components/__tests__/jant-compose-editor.test.ts +484 -20
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +298 -0
- package/src/client/components/__tests__/jant-post-form.test.ts +9 -8
- package/src/client/components/__tests__/jant-post-menu.test.ts +325 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +27 -3
- package/src/client/components/__tests__/jant-settings-general.test.ts +312 -84
- package/src/client/components/collection-manager-types.ts +51 -0
- package/src/client/components/collection-types.ts +12 -10
- package/src/client/components/compose-types.ts +80 -13
- package/src/client/components/jant-collection-form.ts +340 -509
- package/src/client/components/jant-collection-sidebar.ts +474 -463
- package/src/client/components/jant-compose-dialog.ts +1566 -479
- package/src/client/components/jant-compose-editor.ts +531 -276
- package/src/client/components/jant-compose-fullscreen.ts +184 -112
- package/src/client/components/jant-confirm-dialog.ts +249 -0
- package/src/client/components/jant-nav-manager.ts +79 -103
- package/src/client/components/jant-post-form.ts +4 -1
- package/src/client/components/jant-post-menu.ts +583 -306
- package/src/client/components/jant-settings-avatar.ts +15 -9
- package/src/client/components/jant-settings-general.ts +444 -137
- package/src/client/components/nav-manager-types.ts +6 -3
- package/src/client/components/post-form-template.ts +2 -9
- package/src/client/components/post-form-types.ts +2 -4
- package/src/client/components/settings-types.ts +34 -2
- package/src/client/compose-bridge.ts +297 -171
- package/src/client/compose-discovery-bridge.ts +9 -0
- package/src/client/compose-discovery.ts +254 -0
- package/src/client/compose-launch.ts +98 -0
- package/src/client/compose-shortcuts.ts +61 -0
- package/src/client/confirm.ts +40 -0
- package/src/client/custom-url-menu.ts +128 -0
- package/src/client/form-enter-submit.ts +87 -0
- package/src/client/json.ts +37 -0
- package/src/client/multipart-upload.ts +51 -18
- package/src/client/post-form-bridge.ts +20 -14
- package/src/client/runtime-paths.ts +72 -0
- package/src/client/settings-bridge.ts +54 -10
- package/src/client/site-header-nav.js +84 -0
- package/src/client/sortable-list.ts +152 -0
- package/src/client/thread-context.ts +11 -5
- package/src/client/tiptap/__tests__/floating-position.test.ts +74 -0
- package/src/client/tiptap/__tests__/link-toolbar.test.ts +94 -0
- package/src/client/tiptap/__tests__/paste-media.test.ts +39 -0
- package/src/client/tiptap/bubble-menu.ts +98 -13
- package/src/client/tiptap/create-editor.ts +6 -0
- package/src/client/tiptap/extensions.ts +15 -4
- package/src/client/tiptap/floating-position.ts +167 -0
- package/src/client/tiptap/inline-image-upload.ts +73 -0
- package/src/client/tiptap/link-input-rules.ts +56 -0
- package/src/client/tiptap/link-toolbar.ts +60 -14
- package/src/client/tiptap/paste-media.ts +120 -0
- package/src/client/tiptap/slash-commands.ts +205 -52
- package/src/client/tiptap/toolbar-mode.ts +31 -0
- package/src/client/toast.ts +136 -16
- package/src/client/types/sortablejs.d.ts +4 -4
- package/src/client/upload-session.ts +270 -0
- package/src/client/upload-with-metadata.ts +10 -17
- package/src/client-auth.ts +38 -0
- package/src/client.ts +10 -29
- package/src/db/__tests__/d1-query.test.ts +84 -0
- package/src/db/__tests__/demo-canonical-snapshot.test.ts +78 -0
- package/src/db/__tests__/demo-env-loader.test.ts +114 -0
- package/src/db/__tests__/dialect.test.ts +42 -0
- package/src/db/__tests__/migration-artifacts.test.ts +82 -0
- package/src/db/__tests__/migration-rehearsal.test.ts +73 -0
- package/src/db/__tests__/migration-runner.test.ts +72 -0
- package/src/db/__tests__/migrations.test.ts +293 -8
- package/src/db/__tests__/r2-query.test.ts +102 -0
- package/src/db/__tests__/wrangler-config.test.ts +86 -0
- package/src/db/backfills/0000_normalize_legacy_time_zone_values.sql +38 -0
- package/src/db/backfills/README.md +11 -0
- package/src/db/dialect.ts +71 -0
- package/src/db/index.ts +84 -2
- package/src/db/migrations/0000_baseline.sql +35 -57
- package/src/db/migrations/0002_site_aware_core.sql +354 -0
- package/src/db/migrations/0003_fts_site_aware.sql +38 -0
- package/src/db/migrations/0004_perpetual_eternity.sql +29 -0
- package/src/db/migrations/meta/0000_snapshot.json +134 -138
- package/src/db/migrations/meta/0001_snapshot.json +135 -139
- package/src/db/migrations/meta/0002_snapshot.json +1717 -0
- package/src/db/migrations/meta/0003_snapshot.json +1717 -0
- package/src/db/migrations/meta/0004_snapshot.json +1897 -0
- package/src/db/migrations/meta/_journal.json +23 -2
- package/src/db/migrations/pg/0000_pg_site_aware_baseline.sql +306 -0
- package/src/db/migrations/pg/0001_pg_search_support.sql +8 -0
- package/src/db/migrations/pg/0002_breezy_lockjaw.sql +29 -0
- package/src/db/migrations/pg/README.md +8 -0
- package/src/db/migrations/pg/meta/0000_snapshot.json +2195 -0
- package/src/db/migrations/pg/meta/0001_snapshot.json +2248 -0
- package/src/db/migrations/pg/meta/0002_snapshot.json +2476 -0
- package/src/db/migrations/pg/meta/_journal.json +27 -0
- package/src/db/pg/__tests__/node.test.ts +58 -0
- package/src/db/pg/node.ts +151 -0
- package/src/db/pg/schema.ts +739 -0
- package/src/db/raw-query.ts +15 -0
- package/src/db/rehearsal-fixtures/demo-current.json +25 -0
- package/src/db/rehearsal-fixtures/demo-current.sql +142 -0
- package/src/db/schema-bundle.ts +12 -0
- package/src/db/schema.ts +309 -128
- package/src/db/sqlite/schema.ts +1 -0
- package/src/i18n/__tests__/context.test.tsx +35 -0
- package/src/i18n/context.tsx +10 -2
- package/src/i18n/locales/en.po +1792 -231
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/glossary.zh-Hans.yml +116 -0
- package/src/i18n/locales/glossary.zh-Hant.yml +116 -0
- package/src/i18n/locales/zh-Hans.po +1831 -270
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +1835 -274
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +4 -0
- package/src/lib/__tests__/asset-path.test.ts +54 -0
- package/src/lib/__tests__/blurhash-placeholder.test.ts +25 -1
- package/src/lib/__tests__/collection-groups.test.ts +63 -0
- package/src/lib/__tests__/collection-sort.test.ts +45 -0
- package/src/lib/__tests__/constants.test.ts +24 -1
- package/src/lib/__tests__/favicon.test.ts +1 -1
- package/src/lib/__tests__/feed.test.ts +147 -0
- package/src/lib/__tests__/hosted-control-plane.test.ts +106 -0
- package/src/lib/__tests__/hosted-domain.test.ts +78 -0
- package/src/lib/__tests__/hosted-signin.test.ts +103 -0
- package/src/lib/__tests__/icons.test.ts +14 -172
- package/src/lib/__tests__/image.test.ts +23 -4
- package/src/lib/__tests__/jant-branding.test.ts +160 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +27 -0
- package/src/lib/__tests__/page-title.test.ts +20 -0
- package/src/lib/__tests__/password.test.ts +60 -0
- package/src/lib/__tests__/post-display.test.ts +99 -0
- package/src/lib/__tests__/post-meta.test.ts +126 -0
- package/src/lib/__tests__/public-storage.test.ts +75 -0
- package/src/lib/__tests__/resolve-config.test.ts +156 -28
- package/src/lib/__tests__/schemas.test.ts +286 -22
- package/src/lib/__tests__/search-snippet.test.ts +43 -0
- package/src/lib/__tests__/site-resolution.test.ts +39 -0
- package/src/lib/__tests__/slug-format.test.ts +54 -0
- package/src/lib/__tests__/startup-config.test.ts +87 -0
- package/src/lib/__tests__/storage.test.ts +218 -1
- package/src/lib/__tests__/theme.test.ts +50 -13
- package/src/lib/__tests__/time.test.ts +44 -0
- package/src/lib/__tests__/timeline.test.ts +492 -4
- package/src/lib/__tests__/timezones.test.ts +51 -18
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +23 -0
- package/src/lib/__tests__/url.test.ts +79 -1
- package/src/lib/__tests__/view.test.ts +74 -13
- package/src/lib/asset-path.ts +87 -0
- package/src/lib/blurhash-placeholder.ts +56 -1
- package/src/lib/collection-groups.ts +69 -0
- package/src/lib/collection-sort.ts +61 -0
- package/src/lib/confirm.ts +37 -0
- package/src/lib/constants.ts +20 -3
- package/src/lib/crypto.ts +47 -0
- package/src/lib/env.ts +245 -0
- package/src/lib/errors.ts +25 -9
- package/src/lib/favicon.ts +2 -1
- package/src/lib/featured-icons.ts +33 -0
- package/src/lib/feed.ts +73 -24
- package/src/lib/hosted-control-plane-sync.ts +27 -0
- package/src/lib/hosted-control-plane.ts +99 -0
- package/src/lib/hosted-domain-check.ts +98 -0
- package/src/lib/hosted-domain.ts +61 -0
- package/src/lib/hosted-signin.ts +115 -0
- package/src/lib/hosted-sso.ts +110 -0
- package/src/lib/icons.ts +10 -175
- package/src/lib/ids.ts +54 -0
- package/src/lib/image.ts +18 -7
- package/src/lib/jant-branding-generated.ts +947 -0
- package/src/lib/jant-branding.ts +520 -0
- package/src/lib/markdown-to-tiptap.ts +57 -0
- package/src/lib/media-helpers.ts +6 -2
- package/src/lib/navigation.ts +9 -2
- package/src/lib/page-title.ts +23 -0
- package/src/lib/pagination.ts +34 -0
- package/src/lib/password.ts +164 -0
- package/src/lib/post-display.ts +162 -0
- package/src/lib/post-meta.ts +95 -0
- package/src/lib/public-storage.ts +57 -0
- package/src/lib/render.tsx +22 -0
- package/src/lib/resolve-config.ts +78 -27
- package/src/lib/schemas.ts +271 -73
- package/src/lib/search-snippet.ts +110 -8
- package/src/lib/site-resolution.ts +28 -0
- package/src/lib/slug-format.ts +92 -0
- package/src/lib/slug.ts +1 -1
- package/src/lib/startup-config.ts +244 -0
- package/src/lib/storage.ts +722 -28
- package/src/lib/theme.ts +13 -6
- package/src/lib/time.ts +59 -15
- package/src/lib/timeline.ts +394 -63
- package/src/lib/timezones.ts +120 -50
- package/src/lib/tiptap-render.ts +1 -3
- package/src/lib/tiptap-to-markdown.ts +49 -0
- package/src/lib/upload.ts +216 -11
- package/src/lib/url.ts +210 -0
- package/src/lib/view.ts +87 -31
- package/src/middleware/__tests__/auth.test.ts +216 -14
- package/src/middleware/__tests__/error-handler.test.ts +37 -0
- package/src/middleware/__tests__/onboarding.test.ts +9 -0
- package/src/middleware/__tests__/secure-headers.test.ts +111 -0
- package/src/middleware/auth.ts +156 -28
- package/src/middleware/config.ts +29 -9
- package/src/middleware/error-handler.ts +16 -1
- package/src/middleware/onboarding.ts +15 -6
- package/src/middleware/secure-headers.ts +136 -25
- package/src/node/__tests__/cli-db-execute-file.test.ts +109 -0
- package/src/node/__tests__/cli-deploy.test.ts +124 -0
- package/src/node/__tests__/cli-export.test.ts +66 -0
- package/src/node/__tests__/cli-reset-password.test.ts +91 -0
- package/src/node/__tests__/cli-runtime-target.test.ts +37 -0
- package/src/node/__tests__/cli-site-selection.test.ts +133 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +636 -0
- package/src/node/__tests__/cli-site-token-env.test.ts +137 -0
- package/src/node/__tests__/runtime.test.ts +346 -0
- package/src/node/index.ts +18 -0
- package/src/node/request-handler.ts +668 -0
- package/src/node/runtime.ts +88 -0
- package/src/preset.css +73 -16
- package/src/routes/__tests__/compose.test.ts +77 -4
- package/src/routes/api/__tests__/attachments.test.ts +92 -0
- package/src/routes/api/__tests__/collections.test.ts +138 -24
- package/src/routes/api/__tests__/nav-items.test.ts +81 -20
- package/src/routes/api/__tests__/posts.test.ts +315 -57
- package/src/routes/api/__tests__/search.test.ts +25 -1
- package/src/routes/api/__tests__/settings.test.ts +380 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +216 -26
- package/src/routes/api/__tests__/uploads.test.ts +420 -0
- package/src/routes/api/attachments.ts +24 -0
- package/src/routes/api/collections.ts +59 -28
- package/src/routes/api/custom-urls.ts +6 -5
- package/src/routes/api/export.ts +37 -7
- package/src/routes/api/internal/__tests__/api-tokens.test.ts +55 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +472 -0
- package/src/routes/api/internal/__tests__/uploads.test.ts +131 -0
- package/src/routes/api/internal/api-tokens.ts +17 -0
- package/src/routes/api/internal/sites.ts +241 -0
- package/src/routes/api/internal/uploads.ts +37 -0
- package/src/routes/api/nav-items.ts +24 -15
- package/src/routes/api/posts.ts +222 -74
- package/src/routes/api/search.ts +50 -9
- package/src/routes/api/settings.ts +141 -7
- package/src/routes/api/upload-multipart.ts +105 -16
- package/src/routes/api/upload.ts +90 -21
- package/src/routes/api/uploads.ts +247 -0
- package/src/routes/auth/__tests__/dev.test.ts +135 -0
- package/src/routes/auth/__tests__/hosted-sso.test.ts +136 -0
- package/src/routes/auth/__tests__/setup.test.ts +73 -49
- package/src/routes/auth/dev.ts +72 -0
- package/src/routes/auth/hosted-sso.ts +64 -0
- package/src/routes/auth/reset.tsx +41 -7
- package/src/routes/auth/setup.tsx +37 -38
- package/src/routes/auth/signin.tsx +36 -6
- package/src/routes/compose.tsx +25 -38
- package/src/routes/dash/__tests__/font-theme.test.ts +45 -34
- package/src/routes/dash/__tests__/settings-avatar.test.ts +24 -11
- package/src/routes/dash/custom-urls.tsx +245 -67
- package/src/routes/dash/settings.tsx +530 -139
- package/src/routes/feed/__tests__/rss.test.ts +200 -51
- package/src/routes/feed/__tests__/sitemap.test.ts +64 -0
- package/src/routes/feed/rss.ts +112 -48
- package/src/routes/feed/sitemap.ts +16 -6
- package/src/routes/hosted/__tests__/domain-check.test.ts +94 -0
- package/src/routes/hosted/domain-check.ts +39 -0
- package/src/routes/pages/__tests__/collections.test.ts +27 -21
- package/src/routes/pages/__tests__/featured.test.ts +9 -2
- package/src/routes/pages/archive.tsx +79 -33
- package/src/routes/pages/brand.tsx +119 -0
- package/src/routes/pages/collection.tsx +201 -65
- package/src/routes/pages/collections.tsx +46 -20
- package/src/routes/pages/featured.tsx +31 -39
- package/src/routes/pages/home.tsx +50 -25
- package/src/routes/pages/latest.tsx +20 -4
- package/src/routes/pages/new.tsx +10 -6
- package/src/routes/pages/page.tsx +27 -53
- package/src/routes/pages/partials.tsx +83 -0
- package/src/routes/pages/search.tsx +11 -9
- package/src/routes/pages/theme-sample.tsx +98 -0
- package/src/runtime/__tests__/node.test.ts +258 -0
- package/src/runtime/__tests__/readiness.test.ts +89 -0
- package/src/runtime/cloudflare.ts +102 -0
- package/src/runtime/index.ts +14 -0
- package/src/runtime/node.ts +187 -0
- package/src/runtime/readiness.ts +129 -0
- package/src/runtime/site.ts +169 -0
- package/src/services/__tests__/api-token.test.ts +5 -2
- package/src/services/__tests__/auth.test.ts +348 -0
- package/src/services/__tests__/collection.test.ts +199 -16
- package/src/services/__tests__/custom-url.test.ts +10 -3
- package/src/services/__tests__/hosted-handoff.test.ts +123 -0
- package/src/services/__tests__/media.test.ts +256 -33
- package/src/services/__tests__/navigation.test.ts +89 -4
- package/src/services/__tests__/post-timeline.test.ts +169 -2
- package/src/services/__tests__/post.test.ts +498 -39
- package/src/services/__tests__/search.test.ts +145 -14
- package/src/services/__tests__/settings.test.ts +162 -2
- package/src/services/__tests__/site-admin.test.ts +48 -0
- package/src/services/__tests__/site-profile.test.ts +175 -0
- package/src/services/api-token.ts +44 -11
- package/src/services/auth.ts +211 -9
- package/src/services/bootstrap.ts +68 -0
- package/src/services/collection.ts +556 -100
- package/src/services/custom-url.ts +42 -12
- package/src/services/export.ts +2269 -320
- package/src/services/hosted-handoff.ts +184 -0
- package/src/services/index.ts +110 -12
- package/src/services/media.ts +309 -39
- package/src/services/navigation.ts +141 -20
- package/src/services/path.ts +45 -9
- package/src/services/post.ts +1640 -248
- package/src/services/search.ts +222 -29
- package/src/services/settings.ts +202 -48
- package/src/services/site-admin.ts +825 -0
- package/src/services/site-member.ts +74 -0
- package/src/services/site-profile.ts +52 -0
- package/src/services/site.ts +250 -0
- package/src/services/upload-session.ts +707 -0
- package/src/style-cjk-tc.css +3 -0
- package/src/style-cjk.css +3 -0
- package/src/styles/components.css +647 -16
- package/src/styles/fonts/latin.css +4 -0
- package/src/styles/fonts/noto-serif-sc/400/02629e5d0a9860b7fe32ec1f0563213a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/031089da45fbfb7dc18ac827bef4c56e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/03ac785139320b7b13bac9c150bf72bf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/0b1f83a3c7e715560a55ad9eb0fb1c94.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/14b040a2dda256936bc7b3470e548394.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/189f272ea2600c74d576b7b15c014922.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/1fbccc182322b513f57cd156a9a491b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/21e0a71d86be7b8a9e812e7af09dd061.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/265e048cc9f2f8a711ba585a534d5351.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/2ae2ca951489c9d50cde5b36a2a5515b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/2ba802b14f21a58fc61606c88fa93373.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/2deb444546774c3a3ab38c75eb69cdfb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/2fbccf9a3853eb59db1a825e044515fd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/2ff009fa8701505d7f3dc6c83763f019.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/31424fe5d54692e7c8b38021ccb8597c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/360c190344c26278bbc50e2f4d6a2b3f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/39b0bbc910af9d2d6dcd8bd4abd6387d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3adc8f6350cf5067bcb6dc5e44c45d41.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3bed5bd57de8f738e53cddaea88983d9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3c201fd8d1bb20abe7d06b940e83a4d9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3d385ea0880df7204258e290648ec012.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3dc1a4f0d7af59e16b5162a2b077a442.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/3dc68e473fe23bd076dd46785cd23583.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/435b7dca567809813fcb395a27ed83a0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/43693195e775d515689fa035394067fd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/43fb49e5b79ee7e553869d84e6e08b1e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/44dcbbc3cc8f22e613b342d691511ab6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/474fac21b12b7efd71f7c321578878b0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/4a23fe6e82fd496b5eb20401b6164efe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/4bc743968cf1c3ce5711de67ef1ccc4d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/4cf0f292f3358bd2f73b1cf4ec1476f3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/501f66f24bce8234441954de1b568403.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/53a88404451448cd2e620a0ca0e45a20.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/557cd00c5d6827e13d72a0c71b23587b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/563fa31542d553f25abab65cf7f81e1d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/56e1c4734bbbb38af2fbc262bf6e98f2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/5947f5da5da9a352a2b534ee64bfc29a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/5a10741e41259e235841440394c0763d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/60d8b0805a0a8c54a6cca216004beff5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/61bf4287453da4025d03fa6b2dba66ca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/638369541268ed5a10af97ad77498c73.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/687d0f0f90a9b23e40102e16ad8e9836.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/6e2164fad867d166de2e5b274f04a563.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/6eefc9d430171c1e1e4034ecadee31c8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/71eafb8fbe3a734283517e230ad8b6db.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/751f54dbb115140d5b645a6ba4aff5d3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/7609e7e74dd4d916a7abc7ecc7d95f7e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/76b9d6fe838ae4151d95ce7200aa2bf6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/78ce29fed872e44fc9014d94875d2aac.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/79a85a253e9b3f12d2e2cb15e16b3003.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/7caa14a095a6bc313aab780fe4ff7999.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/7d138084cf03c14116b11297fce0e3e3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/880162ae92cd9e120eb4e4e11fae459d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/8f2b960c2823670e94f5b08aa65657e6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/8f89f57230d184f92a36e241874229d7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/9180de34b48b325200a97e267befff32.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/95df3b9f681d9df411c30aea5b24f2e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/975af5a496e8d87d821910aa9fe4d598.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/97a874bbf55ce89a4ab7cd27c7e938b1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/9fd53607094e329fa8e5c785b3ff0f1a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/a578742770fcd2226e3c45b5b6efdcb0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/ae401fb4db80d5ff5cd3f8d9bc811070.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/b0ab3a7f319ce6dd3c9a4de2674e7c72.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/b159deb135e9946eea0572d52778170b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/b91450304d9ac44f5c6e0da0792e055d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/baa325551b381c5e035ef143e56d4abe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/c0c7836749e585cee24ab2f8457c5b01.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/c2ac4ef1860812036ca2b8c4e4089bdc.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/c31019c08bd22464f7a88f090281404c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/c57ee3b49b7e45b995539a6b2c51f138.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/cd75ca47da9ae4c0899e37d4c543319b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d12ce1d8445213317f9163283e58a05d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d28fb13acf9ced9f0657fd4012c81cd2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d3714e6b90de8e2085dfb2514464dd6a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d3beff96216c8af1aa79246476b6a323.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d3e311f30c811dc339c262a79a51877e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d51f4cdc83711e510f5d25f03235597e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/d5df4a8dfd4328c67d933b3912c6ad0f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/dccda6a2e2db3b530788bdfa2acd1979.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f5237486197aeff59341a1ff38b8eff8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f553d54ef931066712d8f3f0ce018e1b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f649cba8e14c33d6bf2265483b14b895.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f6839df1bf7cb4dc8d27e5ea55bbe633.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f6b7304e028980f77a7f7007bb540abd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/f8db8bef0a6e1178835d350ae0d384a1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/fb4649a82c50620773d79820e2e5ff13.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/400/fb9402d6c6357a825affc402f14d5a7e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/0041f681602cc834bb5c55ced0155b8e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/00c9ac960d866ffaf8a866e5939024e2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/02e48e353415a00e0f556608cab33a43.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/02faf6bb0ab4d56ada037c0bbaf9b9f7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/057a3d44d7fc606f113d863376d0ecf0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/10f2b44b3711d3f5bdcc30d373b543d1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/14c1506106d92621bafb11016735194e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/154a2c266902003bd8b7449386b10776.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/19e39850472250bfdbce654d30859879.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/1b5d0d740450fb996749464c9b882025.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/1bca0a2a8840ad0ee9414940593db144.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/1cda27dcaab977ae4ef5d5ab2a10ae03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/1f4bc38a1c50f55f335f5411cae47696.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/2022cf097cb952d9fe75b53b4587d2c3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/2240a3c43ca5ef59ae3c348c7884792f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/280e3d2b58e9ad3501816072e01b0c13.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/2874d07e228da9583b0e73646dacd498.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/29d49891713a2785a3a383001cf58c59.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/2d81eb6ab0ebbc0cabfb3a3341ba8800.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/3179006d1c7ebfa50d27482a2859d9a0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/34c2edb3c37f71258f5c4a31091f0c6c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/389a950f2a1211946d294716e679e381.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/42a74b6a625bbf0a9616ed4db3152c88.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/457485e72835364662dfead6281638c1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/488846410760fe128dae939836ca5423.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/48eb0a91e50c7f026e248c64145e72af.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/4b0e79ba18b2ce424fa93e84996d7cba.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/54e301f412730f391225db59dae1c8d5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/597d69d0710e0178b162afb0a0c20401.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/5cc23a76e122d0ad2f7cede41bc35b27.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/5d48855bed5f3554eff91b573d7376ac.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/5ddcbe564b29ef08632e1aeb33455435.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/605667a998e91e2b6a4a3cd7c31fe5a9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/67f32ceea9e78e5109f87724ad886010.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/68f2fab82ec8e9291f08c3145111549c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/69519ada3f3f74ca20aacb8af48ab6b4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/6a6e884fb2b65ec5b4a3d5ecd0d01a6a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/6e1a8b45b01939088c3a8cfcf8c10681.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/70adaf50c56d5ff859c64d35e0f1e34e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/72ee453ac0e19bd2c631c8921c44e3de.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/753b5f6fb254bacb6618ace25af3df60.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/76d4244186d118eea245d1385a4de2ec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7a86b155111ba20f3e87306ff6beac77.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7b1e76975b0984e6f83e3f9f8069e784.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7b6c60131822a0e4d36d980d52509d4e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7cbda564cb2dd4799ab9e89d51286aa7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7f60eefa15956d6f06dd92404887d58c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/7f8c15e0ecb102738981d9fa4cb6b921.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/812b5a4b87f3a7b4afc1cfebc864f413.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/812dfb7f8144d01b3cc9d5ce0b472f40.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/84742b1ede4f0bb6d27131298eba21b4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/8555f0285e3d28e95e2fc0ccccd9caff.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/8e04e64c8f68d292a18d4160fbde8671.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/958efb9b2fa2ea0008ffef009885f9f8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/9ca9b71010a5faeee7047ef97aeee13b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/9cd0b77920b9d6c64eb686493123fc76.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/9ebd27835ffcbd794e67151ab046ce68.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/a077f51cfb5cffb4ff4d8e229c0e9e79.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/a397997b579d3945c9c70a979c17a8ad.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/a68d9d5027803832bb28e78cdcd04949.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/a904b05966368bcf90b632c7c2e5f76b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/aa218a2c45f3749537ce876201e5152b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/aa28db16818f9eaa8c817f289e1c3270.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/aa96d698491c2540e2dcf7009c65c456.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/ae25c41034ddc1a9e0b41f5034c9aa4b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/ae289ae3f8cdb54a3a6c07174517afec.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/b02dfa2aa52cbdb1b2f11a9f44335469.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/b2e326f7f9b807451bf9c745df747efe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/c33c59feccf391f0c5f1f5d24e36d1fe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/c82fd9456d7465b5e5bd3659e9b14c55.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/c90b7b65d2b9696fbf3a506738f94d68.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/cba6ad3981cb7861428d4be169ee8124.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/cc26525aa2af1f0b929af32ce50a7fba.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/cd0e7b51eddb22a77a09b025c0281434.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/cd6d074f3957d58bac58437fc97e5e33.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/cef249b6d013fb0cc0d574176bc23811.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/d0bd387fda28e58d3c9b3efa2468dd8a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/da93ae099ff3b7aae27b3f674d3fc721.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e264213b9e102dabc603adb6e4fda5e6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e7c7ef3669ae48c0a736f06ca471e1d7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e81a742cacef744130c40de1b90837d8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e8b755172122d1d0a5dd453e96b0ff24.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e99280299c305402eaa5271a3e36c49b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/e9c66b085052ece66bfadf45f711d3e1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/ed7c6dafaa6d8bcf015ef0ca574837df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/f2900a1d30c3a33129f4e2225669bd0e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/fa7d3b99744d7f2dc9e00864a97a62d6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/700/fb0e90665980954719c2eb685b130bc0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-sc/noto-serif-sc.css +3615 -0
- package/src/styles/fonts/noto-serif-tc/400/008ea9091e332c639ceb18874eacd60c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/053bd3d7aec0040d0cc50c261a1f4e3e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/0713613227cc4c686c45a279f8bdc166.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/0e97f44ebc65384c346fe19bcc52fa20.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/1139d32ae2bdeb26c0c8f31330aa9a9f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/15f8c0df47fd639d1b0d9bd5cf507c9b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/1668bd859ffe15bed7d5563117d8d5fb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/190e3f8632494e7c095117f26b1c811e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/19ad151c22ce1befe0a9ea643fbee570.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/1c820b5295868008ca7c78afa5b7655d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/1fbe225742c69f4ba9ea5f74922f0ca1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/2a7cedfcd6e4c7cec36f4fd7b0f329c2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/2acea04a920f6af31e7db97052f563c6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/2e98b666924b8e0a09d1aeeefd24bdd2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/2fd3fceb6faed5e3db768e88d7614dca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/35cf5dd04315e0b906e1a413d7905a2f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/387c811226f303af62f1e21aae6f5c83.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/3b41385fc27419c19822060daa0b5cb3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/3cbe4a697fd595ef42c899de7d3e5445.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/3d83dacbbec3d8532ae9afede21f3aab.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/47479c470fae70f10b7c964a7ecbf274.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/4dc0728df0f2ba70796f45f05654c7ba.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/4dc2bc2c55b47f57d13b63aa6b1c8bd4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/4e1cc6aafb411b572c8d3511e925ecf1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/5227dbe9933760a48baff21ebd13fc98.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/526b263e72c189f4b065738aaa6d423a.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/54da934819a917f561b439bfd10f88b6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/5bfc7a121c35ae42623ef804fb525e0e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/5f90024544c2907c6c0203c6210c50be.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/649b12d7cee7bb981842946e4547e6ca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/653bef2ed891ae48d8ed712283080649.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/67d2a81f06ba352f17fbdc3a5e6ea59e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/68304f3229cf763465f044fccb5892c0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/688a88911e4da17b609196a959b8b930.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/6db6ddf72c38a78ce44c1327701152e1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/77a7533bd21ccd33192d142a93555aa8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/7d65a3d6a65050eb5e6eca43398aeba4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/7dfc711962c8771f97e7c8898a6bcb65.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/7ef123b62d530fcba73974fa265e0aae.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/80466082a896fd328f30a78593c7c568.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/8a3c84b0df36f851f5fea75ee8757951.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/8b0c8c9f8cfa9fa090d97c5a5efb1f4c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/8dc035a34c76e6515ca203e2df182588.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/8eb06109812cb80be44f47b8179c2709.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/904324af375d5fd370af1054355a050e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/911a2092d64d6d6494b254d819af2b91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/9de02d745b8e25c6411fb152fb067748.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/9eb33a430058d839ebbe2af4b2e0daa9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/9f5a73aa8ba417688019d628f334db07.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/a0f0c06d5c7a3ffa97706178cce212a8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/a38c1830367f784181b6f544b0b11bbd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/a9cf85e27428c14351d30eac8cbc8d91.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/aa0ce6740f301351761a0615cc8b2e99.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/bc3f0cb8b55ee11d32b94ca488976f8d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/bcb3307527d6d0033bf0f17660b91e71.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/bf1acc86e17b4229c548828a9d6f455d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/c3fbc1f2557c343863a10698f8c966a2.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/c5c1c0be944ea39a3f50a02d32f5b759.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/c5f1075caf6d1344ee720de85114a521.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/cae29b3f8951eaf20d2f61c2206e28d9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/d043b8d7a48bb0ac59ee1f1477d88eee.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/d320b000b5978c7251148a6a154741b8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/da13b136efb1d1e4c76575af8f79a698.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/da2cf0ec56bf69374ee37764c7e3ea3d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/daf62255dd60679946f28c442ca62533.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/dd01a1035345f6921a48525b8ce08f06.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/e4fb59479cedc87ba79785590bf861ca.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/e5d00355f73293d40b61299459d17ca5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/e647b8d2efc501c0cc0e407249cc7135.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/e6e60ffb2ebd1828628764b507060aea.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/f00eb499abb94fa7b799d6d8c9b050e9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/f7d36ffff7a75c9c6216d576a57dd00d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/f7f3f63e7a149cd89eccab3b52171d05.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/fcc41f6a067ddd658bba5c9dff234a32.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/400/fd6ad889fcf3583bd9b0b6db53aad434.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/0c055db157e7a13f3103cc2a6b67fec3.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/0d3f5cc265cb6c439c517f2c4cebbddf.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/1259e5825b314fe2b8bb96d6e8069ee5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/12c518ebfe62818af550c08947e359e7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/145831a59caa06d894022fe60212ed21.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/169a096e61d38a773216f51d1ec2cc06.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/1884a2b22d314c7d57707f03aec348e0.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/1e2640116bbba817f43c43cc69371cf1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/2573703213da30d3ba18925b100b2c2b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/26839c0e47c73514b8d8f660d24d6b19.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/2a22e14a9ad53f2abb3c7e85017b7d12.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/2b3e8c5703b91f39f6027f43f0da6f4b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/2f27ee4fb2cf6a280e110e09c18ef73e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/31342cebfa5ea7fac06b4ea372d96bc5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/375329ba0b50b94b35006498e555867c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/427577dcb707d1d35eebd155b4222aa7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/450a5b53be0a8a778bb0b623e86b652f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/477866c8396474a17317dcac3e7a014f.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/478ebdaadda7775c391c5dcab4e697df.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/48d6a97a185c799be4fe67aaf7edf213.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/490edb9fc8a4356aea556eed32287464.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4c4bdd0b3f3a52e28f3b643c1c5d43be.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4c96411f3693a9a8657a9c1190f82bce.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4c9aa12aba2a6a57410eacaff7427916.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4cca7233bf8ce5dec2e5d146b993d626.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4d0a9128d06ea857f203bf5d007b1ab9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/4e5384920bbb155d9d8d74887b09ea5b.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/50cfd672bfa62512ba090420acf35c87.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/551b1d7a0b80c8d42af09863cdca7f01.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/555d990ab3fd7d3d66c6d1fa9a82fec5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/5979c33a7eb5963bf8e83e46931b5fb5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/59966ee0b069b577510fe68c350da0ee.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/60a14064ed334f0155795d795e926abe.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/611b62d5fd9698d9b5ce495ba6f14c93.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/6e83fe0b6e708eaf1c3003d6dee11488.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/70861376e5d4f92f8aa7aa1b2749b617.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/7124d150570d39ced8d45507dc11ca1e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/79a7fdf7d9c722b5723ae25e6ff8e203.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/8c8393bc875f1ee36697a2113f4421ea.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/8e6c9bb43afb8cbbff7cf1055e67c9bd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/90ac4f9d2aa02afdace2843b49fc18bb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/90b6f57d77847f512fd11db74fa912f1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/913759e6690f9fc0746a20b96f4bdcb4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/9154e26efe532a85a27d80902f5a2d6c.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/94e7ed67f1557b76fead6b6e456a0415.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/95127a92346c04fec1fa81d6295b0a28.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/9fbc06b2e3ff16b9d705c76db563ef17.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/a3b929542e6c5a0644b73a7c8a8b6c03.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/a8857f5d478f101c053ba02d2f223e90.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/aa64c9953af43ca65832f413895bb433.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/ada8f0241244c60ec8d3d59ad37f20a5.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/b341de0bc0bfe194a6c28dcfb566029e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/b846c293981ca5429eabaa967f222f26.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/be64f9379412876e00fd3a0bfa6b6fe9.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/befed8a4fa817773fa7109db6fe07f56.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/c09ee2b219982f8d46ad9968b7e6e0ba.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/c39ec937c6a8d124e8b68cf829ea5ad4.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/c3fd21315345ae541f6e98067059fa19.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/c568a16e3168ceb1f191b70022c492ea.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/c5e66d60be3375835bbd8d6b797f6eac.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/cd10a3af2133805d8c92104d1ee6ff18.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/d15a3317942b7d31978a759fbf2222c8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/d740dc2e854aaa7b3dcdd3ed25455eeb.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/d8325ba7ae651bc30440905bd67b95f1.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/e2204cf85edcb96c5de5c3dcf240da9d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/e3e913e145ddcd9323b2a0972967feb6.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f2fb1f1fbf7e44afb53c672ec286a22e.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f372129c60aaece937cf7b91ee75c9b8.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f5d7487963d43c89da63aaf10f2e6fb7.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f75496953a40ff241178240209f56990.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f92d74d1d217d21b39075ff23f79f7fd.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/f9d6d981d8b87b3e469027277f585741.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/fa8ed469ef290bfeb571418fe0abb628.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/fb61b690208eff56e6d8432951270901.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/700/ff0937ad63cda71ff420945ead55ab4d.woff2 +0 -0
- package/src/styles/fonts/noto-serif-tc/noto-serif-tc.css +3104 -0
- package/src/styles/tokens.css +42 -7
- package/src/styles/ui.css +7597 -3395
- package/src/types/app-context.ts +7 -0
- package/src/types/bindings.ts +77 -27
- package/src/types/config.ts +104 -4
- package/src/types/constants.ts +43 -4
- package/src/types/entities.ts +63 -4
- package/src/types/operations.ts +45 -15
- package/src/types/props.ts +48 -10
- package/src/types/views.ts +51 -8
- package/src/ui/__tests__/color-themes.test.ts +81 -0
- package/src/ui/__tests__/font-themes.test.ts +76 -7
- package/src/ui/color-themes.ts +424 -238
- package/src/ui/compose/ComposeDialog.tsx +149 -91
- package/src/ui/compose/ComposePrompt.tsx +20 -3
- package/src/ui/dash/ActionButtons.tsx +16 -2
- package/src/ui/dash/DangerZone.tsx +10 -1
- package/src/ui/dash/StatusBadge.tsx +3 -3
- package/src/ui/dash/appearance/AdvancedContent.tsx +9 -2
- package/src/ui/dash/appearance/ColorThemeContent.tsx +326 -63
- package/src/ui/dash/appearance/FontThemeContent.tsx +75 -17
- package/src/ui/dash/appearance/NavigationContent.tsx +48 -21
- package/src/ui/dash/settings/AccountContent.tsx +7 -2
- package/src/ui/dash/settings/AccountMenuContent.tsx +242 -111
- package/src/ui/dash/settings/ApiTokensContent.tsx +38 -8
- package/src/ui/dash/settings/AvatarContent.tsx +1 -1
- package/src/ui/dash/settings/DeleteAccountContent.tsx +300 -0
- package/src/ui/dash/settings/GeneralContent.tsx +120 -6
- package/src/ui/dash/settings/SessionsContent.tsx +23 -5
- package/src/ui/dash/settings/SettingsDirectory.tsx +99 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +176 -221
- package/src/ui/feed/CuratedThreadPreview.tsx +73 -0
- package/src/ui/feed/LinkCard.tsx +52 -36
- package/src/ui/feed/NoteCard.tsx +10 -4
- package/src/ui/feed/PostStatusBadges.tsx +4 -21
- package/src/ui/feed/QuoteCard.tsx +11 -3
- package/src/ui/feed/ThreadPreview.tsx +11 -5
- package/src/ui/feed/TimelineFeed.tsx +53 -15
- package/src/ui/feed/__tests__/timeline-cards.test.ts +172 -0
- package/src/ui/feed/thread-preview-state.ts +2 -4
- package/src/ui/font-themes.ts +211 -43
- package/src/ui/layouts/BaseLayout.tsx +161 -30
- package/src/ui/layouts/SiteLayout.tsx +51 -18
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +169 -0
- package/src/ui/pages/ArchivePage.tsx +320 -120
- package/src/ui/pages/BrandPage.tsx +927 -0
- package/src/ui/pages/CollectionEditorPage.tsx +155 -0
- package/src/ui/pages/CollectionPage.tsx +482 -23
- package/src/ui/pages/CollectionsPage.tsx +57 -57
- package/src/ui/pages/FeaturedPage.tsx +24 -3
- package/src/ui/pages/HomePage.tsx +13 -0
- package/src/ui/pages/PostPage.tsx +13 -4
- package/src/ui/pages/SearchPage.tsx +12 -2
- package/src/ui/pages/ThemeSamplePage.tsx +1160 -0
- package/src/ui/shared/AdminBreadcrumb.tsx +22 -6
- package/src/ui/shared/CollectionDirectory.tsx +131 -0
- package/src/ui/shared/CollectionsManager.tsx +277 -0
- package/src/ui/shared/DecorativeQuoteMark.tsx +32 -0
- package/src/ui/shared/HomePageBranding.tsx +22 -0
- package/src/ui/shared/JantBrandMark.tsx +35 -0
- package/src/ui/shared/MediaGallery.tsx +106 -36
- package/src/ui/shared/PaginatedPageHeader.tsx +49 -0
- package/src/ui/shared/Pagination.tsx +2 -6
- package/src/ui/shared/PostFooter.tsx +130 -67
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +14 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +82 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +103 -0
- package/src/ui/shared/__tests__/pagination.test.ts +18 -1
- package/src/ui/shared/__tests__/post-footer.test.ts +122 -0
- package/src/ui/shared/collection-management-labels.ts +161 -0
- package/src/ui/shared/navigation-labels.ts +126 -0
- package/src/vendor/datastar.js +1955 -7
- package/dist/client/assets/heic-to-DIRPI3VF.js +0 -1
- package/dist/client/assets/module-RjUF93sV.js +0 -716
- package/dist/client/assets/native-48B9X9Wg.js +0 -1
- package/dist/client/assets/url-FWFqPJPb.js +0 -1
- package/dist/client/client.css +0 -1
- package/dist/client/client.js +0 -33565
- package/src/client/components/collection-sidebar-types.ts +0 -43
- package/src/client/tiptap/paste-image.ts +0 -129
- package/src/lib/emoji-catalog.ts +0 -963
- package/src/lib/icon-catalog.ts +0 -5213
- package/src/ui/shared/CollectionsSidebar.tsx +0 -294
package/src/services/post.ts
CHANGED
|
@@ -3,29 +3,58 @@
|
|
|
3
3
|
*
|
|
4
4
|
* CRUD operations for posts with Thread support.
|
|
5
5
|
* Posts have format (note/link/quote), status (draft/published),
|
|
6
|
-
* visibility (public/
|
|
6
|
+
* visibility (public/latest_hidden/private), featuredAt, and pinnedAt timestamp.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import {
|
|
10
|
+
eq,
|
|
11
|
+
and,
|
|
12
|
+
type SQL,
|
|
13
|
+
type SQLWrapper,
|
|
14
|
+
isNull,
|
|
15
|
+
desc,
|
|
16
|
+
inArray,
|
|
17
|
+
sql,
|
|
18
|
+
isNotNull,
|
|
19
|
+
asc,
|
|
20
|
+
lte,
|
|
21
|
+
} from "drizzle-orm";
|
|
22
|
+
import {
|
|
23
|
+
type Database,
|
|
24
|
+
batchQueryRows,
|
|
25
|
+
supportsDrizzleTransaction,
|
|
26
|
+
} from "../db/index.js";
|
|
27
|
+
import type { DatabaseDialect } from "../db/dialect.js";
|
|
28
|
+
import {
|
|
29
|
+
sqliteSchemaBundle,
|
|
30
|
+
type DatabaseSchema,
|
|
31
|
+
} from "../db/schema-bundle.js";
|
|
32
|
+
import { createEntityId } from "../lib/ids.js";
|
|
14
33
|
import { now } from "../lib/time.js";
|
|
15
34
|
import { renderTiptapJson } from "../lib/tiptap-render.js";
|
|
16
35
|
import { extractSummary, extractBodyText } from "../lib/summary.js";
|
|
17
36
|
import { markdownToTiptapJson } from "../lib/markdown-to-tiptap.js";
|
|
18
37
|
import { generatePostSlug } from "../lib/slug.js";
|
|
38
|
+
import { getSlugValidationIssue } from "../lib/slug-format.js";
|
|
19
39
|
import { normalizePath, slugify } from "../lib/url.js";
|
|
20
40
|
import type { StorageDriver } from "../lib/storage.js";
|
|
21
41
|
import type { MediaService } from "./media.js";
|
|
42
|
+
import {
|
|
43
|
+
FORMATS,
|
|
44
|
+
MAX_MEDIA_ATTACHMENTS,
|
|
45
|
+
STATUSES,
|
|
46
|
+
VISIBILITIES,
|
|
47
|
+
} from "../types.js";
|
|
22
48
|
import type {
|
|
49
|
+
CollectionSortOrder,
|
|
23
50
|
Format,
|
|
24
51
|
Status,
|
|
25
52
|
Visibility,
|
|
53
|
+
SortOrder,
|
|
26
54
|
MediaKind,
|
|
27
55
|
Post,
|
|
28
56
|
CreatePost,
|
|
57
|
+
PostAttachmentInput,
|
|
29
58
|
UpdatePost,
|
|
30
59
|
ThreadTimelineContext,
|
|
31
60
|
} from "../types.js";
|
|
@@ -42,6 +71,11 @@ export interface PostDeleteDeps {
|
|
|
42
71
|
storage?: StorageDriver | null;
|
|
43
72
|
}
|
|
44
73
|
|
|
74
|
+
export interface PostAttachmentDeps extends PostDeleteDeps {
|
|
75
|
+
storageDriver: string;
|
|
76
|
+
maxFileSizeMB: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
45
79
|
export interface PostFilters {
|
|
46
80
|
format?: Format;
|
|
47
81
|
status?: Status;
|
|
@@ -49,10 +83,11 @@ export interface PostFilters {
|
|
|
49
83
|
pinned?: boolean;
|
|
50
84
|
featured?: boolean;
|
|
51
85
|
collectionId?: string;
|
|
86
|
+
collectionIds?: string[];
|
|
52
87
|
/** Exclude posts that are replies (have replyToId set) */
|
|
53
88
|
excludeReplies?: boolean;
|
|
54
|
-
/** Exclude
|
|
55
|
-
|
|
89
|
+
/** Exclude posts hidden from Latest from results */
|
|
90
|
+
excludeLatestHidden?: boolean;
|
|
56
91
|
/** Exclude private posts from results */
|
|
57
92
|
excludePrivate?: boolean;
|
|
58
93
|
includeDeleted?: boolean;
|
|
@@ -67,8 +102,12 @@ export interface PostFilters {
|
|
|
67
102
|
hasMedia?: boolean;
|
|
68
103
|
/** Filter by title presence */
|
|
69
104
|
hasTitle?: boolean;
|
|
105
|
+
/** Filter by rating presence */
|
|
106
|
+
hasRating?: boolean;
|
|
107
|
+
/** Explicit result sort order */
|
|
108
|
+
sortOrder?: SortOrder;
|
|
70
109
|
limit?: number;
|
|
71
|
-
cursor?: string;
|
|
110
|
+
cursor?: string;
|
|
72
111
|
offset?: number; // offset for page-based pagination
|
|
73
112
|
}
|
|
74
113
|
|
|
@@ -78,18 +117,65 @@ export interface SummaryConfig {
|
|
|
78
117
|
maxChars: number;
|
|
79
118
|
}
|
|
80
119
|
|
|
120
|
+
interface ThreadRootPageOptions {
|
|
121
|
+
status?: Status;
|
|
122
|
+
excludePrivate?: boolean;
|
|
123
|
+
limit?: number;
|
|
124
|
+
offset?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface CollectionThreadRootPageOptions extends ThreadRootPageOptions {
|
|
128
|
+
sortOrder?: CollectionSortOrder;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface CursorSortKey {
|
|
132
|
+
direction: "asc" | "desc";
|
|
133
|
+
expr: SQLWrapper;
|
|
134
|
+
value: number | string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CollectionFeedEntry {
|
|
138
|
+
post: Post;
|
|
139
|
+
collectedAt: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
81
142
|
export interface PostService {
|
|
82
143
|
getById(id: string): Promise<Post | null>;
|
|
83
144
|
getBySlug(slug: string): Promise<Post | null>;
|
|
145
|
+
suggestSlug(input: {
|
|
146
|
+
title?: string;
|
|
147
|
+
slug?: string;
|
|
148
|
+
excludePostId?: string;
|
|
149
|
+
}): Promise<string>;
|
|
150
|
+
checkSlugAvailability(slug: string, excludePostId?: string): Promise<boolean>;
|
|
84
151
|
list(filters?: PostFilters): Promise<Post[]>;
|
|
85
152
|
/** Count posts matching filters (ignores cursor, offset, limit) */
|
|
86
153
|
count(filters?: PostFilters): Promise<number>;
|
|
154
|
+
/** Count posts matching filters up to a fixed limit (ignores cursor, offset, limit) */
|
|
155
|
+
countUpTo(filters: PostFilters | undefined, limit: number): Promise<number>;
|
|
156
|
+
/** Count posts grouped by published year-month (YYYY-MM) */
|
|
157
|
+
countByYearMonth(
|
|
158
|
+
filters?: PostFilters,
|
|
159
|
+
): Promise<{ yearMonth: string; count: number }[]>;
|
|
87
160
|
create(data: CreatePost, summaryConfig?: SummaryConfig): Promise<Post>;
|
|
161
|
+
createWithAttachments(
|
|
162
|
+
data: CreatePost,
|
|
163
|
+
attachments: PostAttachmentInput[] | undefined,
|
|
164
|
+
deps: PostAttachmentDeps,
|
|
165
|
+
summaryConfig?: SummaryConfig,
|
|
166
|
+
): Promise<Post>;
|
|
88
167
|
update(
|
|
89
168
|
id: string,
|
|
90
169
|
data: UpdatePost,
|
|
91
170
|
summaryConfig?: SummaryConfig,
|
|
92
171
|
): Promise<Post | null>;
|
|
172
|
+
updateWithAttachments(
|
|
173
|
+
id: string,
|
|
174
|
+
data: UpdatePost,
|
|
175
|
+
attachments: PostAttachmentInput[] | undefined,
|
|
176
|
+
deps: PostAttachmentDeps,
|
|
177
|
+
summaryConfig?: SummaryConfig,
|
|
178
|
+
): Promise<Post | null>;
|
|
93
179
|
/**
|
|
94
180
|
* Soft-delete a post and clean up its media (storage files + DB records).
|
|
95
181
|
* Thread roots cascade to all replies.
|
|
@@ -115,6 +201,52 @@ export interface PostService {
|
|
|
115
201
|
getThreadTimelineContext(
|
|
116
202
|
rootIds: string[],
|
|
117
203
|
): Promise<Map<string, ThreadTimelineContext>>;
|
|
204
|
+
/** Count distinct thread roots that contain featured published posts */
|
|
205
|
+
countFeaturedThreadRoots(options?: ThreadRootPageOptions): Promise<number>;
|
|
206
|
+
/** List featured thread root IDs ordered by the latest featured post in each thread */
|
|
207
|
+
listFeaturedThreadRootIds(options?: ThreadRootPageOptions): Promise<string[]>;
|
|
208
|
+
/** Count distinct thread roots that contain published posts in the given collection */
|
|
209
|
+
countCollectionThreadRoots(
|
|
210
|
+
collectionId: string,
|
|
211
|
+
options?: ThreadRootPageOptions,
|
|
212
|
+
): Promise<number>;
|
|
213
|
+
/** Count distinct thread roots that contain published posts in any of the given collections */
|
|
214
|
+
countCollectionThreadRootsForCollections(
|
|
215
|
+
collectionIds: string[],
|
|
216
|
+
options?: ThreadRootPageOptions,
|
|
217
|
+
): Promise<number>;
|
|
218
|
+
/** List collection thread root IDs ordered by collected-at or rating semantics */
|
|
219
|
+
listCollectionThreadRootIds(
|
|
220
|
+
collectionId: string,
|
|
221
|
+
options?: CollectionThreadRootPageOptions,
|
|
222
|
+
): Promise<string[]>;
|
|
223
|
+
/** List collection thread root IDs for a union of collections */
|
|
224
|
+
listCollectionThreadRootIdsForCollections(
|
|
225
|
+
collectionIds: string[],
|
|
226
|
+
options?: CollectionThreadRootPageOptions,
|
|
227
|
+
): Promise<string[]>;
|
|
228
|
+
/** List collection feed entries ordered by latest added-at timestamp */
|
|
229
|
+
listCollectionFeedEntries(
|
|
230
|
+
collectionId: string,
|
|
231
|
+
options?: ThreadRootPageOptions,
|
|
232
|
+
): Promise<CollectionFeedEntry[]>;
|
|
233
|
+
/** List collection feed entries for a union of collections */
|
|
234
|
+
listCollectionFeedEntriesForCollections(
|
|
235
|
+
collectionIds: string[],
|
|
236
|
+
options?: ThreadRootPageOptions,
|
|
237
|
+
): Promise<CollectionFeedEntry[]>;
|
|
238
|
+
/** Fetch all published, non-deleted posts for each requested thread root */
|
|
239
|
+
getPublishedThreads(rootIds: string[]): Promise<Map<string, Post[]>>;
|
|
240
|
+
/** For each thread, return post IDs that belong to the given collection */
|
|
241
|
+
getCollectionPostIdsByThread(
|
|
242
|
+
collectionId: string,
|
|
243
|
+
threadIds: string[],
|
|
244
|
+
): Promise<Map<string, string[]>>;
|
|
245
|
+
/** For each thread, return post IDs that belong to any of the given collections */
|
|
246
|
+
getCollectionPostIdsByThreadForCollections(
|
|
247
|
+
collectionIds: string[],
|
|
248
|
+
threadIds: string[],
|
|
249
|
+
): Promise<Map<string, string[]>>;
|
|
118
250
|
/** Get distinct years that have published posts */
|
|
119
251
|
getDistinctYears(filters?: PostFilters): Promise<number[]>;
|
|
120
252
|
/** For each thread ID, return the ID of the last published, non-deleted post */
|
|
@@ -150,11 +282,77 @@ function hasNonEmptyText(value: string | null | undefined): boolean {
|
|
|
150
282
|
return typeof value === "string" && value.trim().length > 0;
|
|
151
283
|
}
|
|
152
284
|
|
|
285
|
+
function ensureAllowedPostValue<T extends string>(
|
|
286
|
+
value: string,
|
|
287
|
+
allowed: readonly T[],
|
|
288
|
+
message: string,
|
|
289
|
+
ErrorCtor: new (message: string) => Error = ValidationError,
|
|
290
|
+
): T {
|
|
291
|
+
if ((allowed as readonly string[]).includes(value)) {
|
|
292
|
+
return value as T;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
throw new ErrorCtor(message);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function ensurePostFormat(
|
|
299
|
+
value: string,
|
|
300
|
+
ErrorCtor: new (message: string) => Error = ValidationError,
|
|
301
|
+
): Format {
|
|
302
|
+
return ensureAllowedPostValue(
|
|
303
|
+
value,
|
|
304
|
+
FORMATS,
|
|
305
|
+
"Format must be note, link, or quote.",
|
|
306
|
+
ErrorCtor,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function ensurePostStatus(
|
|
311
|
+
value: string,
|
|
312
|
+
ErrorCtor: new (message: string) => Error = ValidationError,
|
|
313
|
+
): Status {
|
|
314
|
+
return ensureAllowedPostValue(
|
|
315
|
+
value,
|
|
316
|
+
STATUSES,
|
|
317
|
+
"Status must be draft or published.",
|
|
318
|
+
ErrorCtor,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function ensurePostVisibility(
|
|
323
|
+
value: string,
|
|
324
|
+
ErrorCtor: new (message: string) => Error = ValidationError,
|
|
325
|
+
): Visibility {
|
|
326
|
+
return ensureAllowedPostValue(
|
|
327
|
+
value,
|
|
328
|
+
VISIBILITIES,
|
|
329
|
+
"Visibility must be public, hidden from Latest, or private.",
|
|
330
|
+
ErrorCtor,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function ensurePostRating(
|
|
335
|
+
value: number | null | undefined,
|
|
336
|
+
ErrorCtor: new (message: string) => Error = ValidationError,
|
|
337
|
+
): number | null {
|
|
338
|
+
if (value === null || value === undefined) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (Number.isInteger(value) && value >= 1 && value <= 5) {
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
throw new ErrorCtor("Rating must be an integer between 1 and 5.");
|
|
347
|
+
}
|
|
348
|
+
|
|
153
349
|
function assertPostFormatShape(data: {
|
|
154
350
|
format: Format;
|
|
351
|
+
title?: string | null;
|
|
155
352
|
url?: string | null;
|
|
156
353
|
quoteText?: string | null;
|
|
157
354
|
}): void {
|
|
355
|
+
const hasTitle = hasNonEmptyText(data.title);
|
|
158
356
|
const hasUrl = hasNonEmptyText(data.url);
|
|
159
357
|
const hasQuoteText = hasNonEmptyText(data.quoteText);
|
|
160
358
|
|
|
@@ -169,6 +367,9 @@ function assertPostFormatShape(data: {
|
|
|
169
367
|
}
|
|
170
368
|
|
|
171
369
|
if (data.format === "link") {
|
|
370
|
+
if (!hasTitle) {
|
|
371
|
+
throw new ValidationError("Link posts need a title.");
|
|
372
|
+
}
|
|
172
373
|
if (!hasUrl) {
|
|
173
374
|
throw new ValidationError("Link posts need a URL.");
|
|
174
375
|
}
|
|
@@ -198,24 +399,65 @@ function assertDraftPublishedAt(
|
|
|
198
399
|
|
|
199
400
|
export function createPostService(
|
|
200
401
|
db: Database,
|
|
201
|
-
config: {
|
|
202
|
-
|
|
402
|
+
config: {
|
|
403
|
+
slugIdLength: number;
|
|
404
|
+
databaseDialect?: DatabaseDialect;
|
|
405
|
+
},
|
|
406
|
+
siteId: string,
|
|
407
|
+
paths: PathService | undefined,
|
|
408
|
+
databaseSchema: DatabaseSchema = sqliteSchemaBundle,
|
|
203
409
|
): PostService {
|
|
410
|
+
const resolvedPaths = paths ?? createPathService(db, siteId, databaseSchema);
|
|
411
|
+
const { pathRegistry, posts, postCollections } = databaseSchema;
|
|
412
|
+
const databaseDialect = config.databaseDialect ?? "sqlite";
|
|
413
|
+
const usesBatchWrites = !supportsDrizzleTransaction(db, databaseDialect);
|
|
414
|
+
|
|
415
|
+
function buildPublishedYearMonthExpr(): SQL<string> {
|
|
416
|
+
return databaseDialect === "pg"
|
|
417
|
+
? sql<string>`to_char(timezone('UTC', to_timestamp(${posts.publishedAt})), 'YYYY-MM')`
|
|
418
|
+
: sql<string>`strftime('%Y-%m', ${posts.publishedAt}, 'unixepoch')`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function buildPublishedYearExpr(): SQL<string> {
|
|
422
|
+
return databaseDialect === "pg"
|
|
423
|
+
? sql<string>`to_char(timezone('UTC', to_timestamp(${posts.publishedAt})), 'YYYY')`
|
|
424
|
+
: sql<string>`strftime('%Y', ${posts.publishedAt}, 'unixepoch')`;
|
|
425
|
+
}
|
|
426
|
+
|
|
204
427
|
const effectiveVisibilityExpr = sql<string>`coalesce(
|
|
205
428
|
${posts.visibility},
|
|
206
429
|
(SELECT root.visibility FROM post AS root WHERE root.id = ${posts.threadId})
|
|
207
430
|
)`;
|
|
208
431
|
|
|
209
|
-
/** Check if a slug is available (not used by posts or
|
|
432
|
+
/** Check if a slug is available (not used by posts or path_registry) */
|
|
210
433
|
async function isSlugAvailable(slug: string): Promise<boolean> {
|
|
211
|
-
return
|
|
434
|
+
return resolvedPaths.isPathAvailable(slug);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function isSlugAvailableForPost(
|
|
438
|
+
slug: string,
|
|
439
|
+
excludePostId?: string,
|
|
440
|
+
): Promise<boolean> {
|
|
441
|
+
const resolved = await resolvedPaths.resolve(slug);
|
|
442
|
+
if (!resolved) return true;
|
|
443
|
+
|
|
444
|
+
return Boolean(
|
|
445
|
+
excludePostId &&
|
|
446
|
+
resolved.kind === "slug" &&
|
|
447
|
+
resolved.postId === excludePostId,
|
|
448
|
+
);
|
|
212
449
|
}
|
|
213
450
|
|
|
214
451
|
async function pathExists(path: string): Promise<boolean> {
|
|
215
452
|
const rows = await db
|
|
216
453
|
.select({ id: pathRegistry.id })
|
|
217
454
|
.from(pathRegistry)
|
|
218
|
-
.where(
|
|
455
|
+
.where(
|
|
456
|
+
and(
|
|
457
|
+
eq(pathRegistry.siteId, siteId),
|
|
458
|
+
eq(pathRegistry.path, normalizePath(path)),
|
|
459
|
+
),
|
|
460
|
+
)
|
|
219
461
|
.limit(1);
|
|
220
462
|
return rows.length > 0;
|
|
221
463
|
}
|
|
@@ -228,23 +470,69 @@ export function createPostService(
|
|
|
228
470
|
),
|
|
229
471
|
})
|
|
230
472
|
.from(posts)
|
|
231
|
-
.where(
|
|
473
|
+
.where(
|
|
474
|
+
and(
|
|
475
|
+
eq(posts.siteId, siteId),
|
|
476
|
+
eq(posts.threadId, rootId),
|
|
477
|
+
isNull(posts.deletedAt),
|
|
478
|
+
),
|
|
479
|
+
);
|
|
232
480
|
|
|
233
481
|
const latestPublishedAt = rootRows[0]?.latestPublishedAt ?? null;
|
|
234
482
|
const root = await db
|
|
235
483
|
.select({ updatedAt: posts.updatedAt })
|
|
236
484
|
.from(posts)
|
|
237
|
-
.where(eq(posts.id, rootId))
|
|
485
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, rootId)))
|
|
238
486
|
.limit(1);
|
|
239
487
|
|
|
240
488
|
const lastActivityAt = latestPublishedAt ?? root[0]?.updatedAt ?? now();
|
|
241
489
|
|
|
242
|
-
await db
|
|
490
|
+
await db
|
|
491
|
+
.update(posts)
|
|
492
|
+
.set({ lastActivityAt })
|
|
493
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, rootId)));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function normalizeCollectionIds(collectionIds: readonly string[]): string[] {
|
|
497
|
+
return [...new Set(collectionIds)];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function buildCollectionMembershipCondition(
|
|
501
|
+
collectionIds: readonly string[],
|
|
502
|
+
): SQL<unknown> {
|
|
503
|
+
const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
|
|
504
|
+
const firstCollectionId = uniqueCollectionIds[0];
|
|
505
|
+
if (!firstCollectionId) {
|
|
506
|
+
return sql`0 = 1`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return uniqueCollectionIds.length === 1
|
|
510
|
+
? eq(postCollections.collectionId, firstCollectionId)
|
|
511
|
+
: inArray(postCollections.collectionId, uniqueCollectionIds);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function buildPostCollectionSubqueryCondition(
|
|
515
|
+
collectionIds: readonly string[],
|
|
516
|
+
): SQL<unknown> {
|
|
517
|
+
const uniqueCollectionIds = normalizeCollectionIds(collectionIds);
|
|
518
|
+
if (uniqueCollectionIds.length === 0) {
|
|
519
|
+
return sql`0 = 1`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const placeholders = uniqueCollectionIds.map(
|
|
523
|
+
(collectionId) => sql`${collectionId}`,
|
|
524
|
+
);
|
|
525
|
+
return sql`${posts.id} IN (
|
|
526
|
+
SELECT post_id
|
|
527
|
+
FROM post_collection
|
|
528
|
+
WHERE site_id = ${siteId}
|
|
529
|
+
AND collection_id IN (${sql.join(placeholders, sql`, `)})
|
|
530
|
+
)`;
|
|
243
531
|
}
|
|
244
532
|
|
|
245
533
|
/** Build WHERE conditions from filters (shared by list and count) */
|
|
246
534
|
function buildFilterConditions(filters: PostFilters) {
|
|
247
|
-
const conditions = [];
|
|
535
|
+
const conditions = [eq(posts.siteId, siteId)];
|
|
248
536
|
|
|
249
537
|
if (filters.status) {
|
|
250
538
|
conditions.push(eq(posts.status, filters.status));
|
|
@@ -252,8 +540,8 @@ export function createPostService(
|
|
|
252
540
|
if (filters.visibility !== undefined) {
|
|
253
541
|
conditions.push(sql`${effectiveVisibilityExpr} = ${filters.visibility}`);
|
|
254
542
|
}
|
|
255
|
-
if (filters.
|
|
256
|
-
conditions.push(sql`${effectiveVisibilityExpr} != '
|
|
543
|
+
if (filters.excludeLatestHidden) {
|
|
544
|
+
conditions.push(sql`${effectiveVisibilityExpr} != 'latest_hidden'`);
|
|
257
545
|
}
|
|
258
546
|
if (filters.excludePrivate) {
|
|
259
547
|
conditions.push(sql`${effectiveVisibilityExpr} != 'private'`);
|
|
@@ -275,10 +563,13 @@ export function createPostService(
|
|
|
275
563
|
if (filters.format) {
|
|
276
564
|
conditions.push(eq(posts.format, filters.format));
|
|
277
565
|
}
|
|
278
|
-
if (filters.
|
|
279
|
-
// Filter by collection via junction table
|
|
566
|
+
if (filters.collectionIds !== undefined) {
|
|
280
567
|
conditions.push(
|
|
281
|
-
|
|
568
|
+
buildPostCollectionSubqueryCondition(filters.collectionIds),
|
|
569
|
+
);
|
|
570
|
+
} else if (filters.collectionId !== undefined) {
|
|
571
|
+
conditions.push(
|
|
572
|
+
buildPostCollectionSubqueryCondition([filters.collectionId]),
|
|
282
573
|
);
|
|
283
574
|
}
|
|
284
575
|
if (filters.threadId) {
|
|
@@ -299,14 +590,27 @@ export function createPostService(
|
|
|
299
590
|
if (filters.mediaKinds && filters.mediaKinds.length > 0) {
|
|
300
591
|
const placeholders = filters.mediaKinds.map((k) => sql`${k}`);
|
|
301
592
|
conditions.push(
|
|
302
|
-
sql`${posts.id} IN (
|
|
593
|
+
sql`${posts.id} IN (
|
|
594
|
+
SELECT post_id
|
|
595
|
+
FROM media
|
|
596
|
+
WHERE site_id = ${siteId}
|
|
597
|
+
AND media_kind IN (${sql.join(placeholders, sql`, `)})
|
|
598
|
+
)`,
|
|
303
599
|
);
|
|
304
600
|
}
|
|
305
601
|
if (filters.hasMedia !== undefined) {
|
|
306
602
|
if (filters.hasMedia) {
|
|
307
|
-
conditions.push(
|
|
603
|
+
conditions.push(
|
|
604
|
+
sql`${posts.id} IN (
|
|
605
|
+
SELECT post_id FROM media WHERE site_id = ${siteId}
|
|
606
|
+
)`,
|
|
607
|
+
);
|
|
308
608
|
} else {
|
|
309
|
-
conditions.push(
|
|
609
|
+
conditions.push(
|
|
610
|
+
sql`${posts.id} NOT IN (
|
|
611
|
+
SELECT post_id FROM media WHERE site_id = ${siteId}
|
|
612
|
+
)`,
|
|
613
|
+
);
|
|
310
614
|
}
|
|
311
615
|
}
|
|
312
616
|
if (filters.hasTitle !== undefined) {
|
|
@@ -318,20 +622,163 @@ export function createPostService(
|
|
|
318
622
|
conditions.push(sql`(${posts.title} IS NULL OR ${posts.title} = '')`);
|
|
319
623
|
}
|
|
320
624
|
}
|
|
625
|
+
if (filters.hasRating !== undefined) {
|
|
626
|
+
conditions.push(
|
|
627
|
+
filters.hasRating ? isNotNull(posts.rating) : isNull(posts.rating),
|
|
628
|
+
);
|
|
629
|
+
}
|
|
321
630
|
|
|
322
631
|
return conditions;
|
|
323
632
|
}
|
|
324
633
|
|
|
634
|
+
function getCursorSortTimestamp(row: typeof posts.$inferSelect): number {
|
|
635
|
+
return row.status === "draft" ? row.updatedAt : (row.lastActivityAt ?? -1);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function buildLexicographicCursorCondition(
|
|
639
|
+
keys: [CursorSortKey, ...CursorSortKey[]],
|
|
640
|
+
): SQL<unknown> {
|
|
641
|
+
const [first, ...rest] = keys;
|
|
642
|
+
const comparison =
|
|
643
|
+
first.direction === "desc"
|
|
644
|
+
? sql`${first.expr} < ${first.value}`
|
|
645
|
+
: sql`${first.expr} > ${first.value}`;
|
|
646
|
+
|
|
647
|
+
if (rest.length === 0) {
|
|
648
|
+
return comparison;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return sql`(
|
|
652
|
+
${comparison}
|
|
653
|
+
OR (${first.expr} = ${first.value} AND ${buildLexicographicCursorCondition(
|
|
654
|
+
rest as [CursorSortKey, ...CursorSortKey[]],
|
|
655
|
+
)})
|
|
656
|
+
)`;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function buildListCursorCondition(
|
|
660
|
+
filters: PostFilters,
|
|
661
|
+
): Promise<SQL<unknown> | null> {
|
|
662
|
+
if (!filters.cursor) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const cursorRow = await db
|
|
667
|
+
.select()
|
|
668
|
+
.from(posts)
|
|
669
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, filters.cursor)))
|
|
670
|
+
.limit(1);
|
|
671
|
+
const cursorPost = cursorRow[0];
|
|
672
|
+
|
|
673
|
+
if (!cursorPost) {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const sortTimestampExpr =
|
|
678
|
+
filters.status === "draft"
|
|
679
|
+
? posts.updatedAt
|
|
680
|
+
: filters.status === "published"
|
|
681
|
+
? posts.lastActivityAt
|
|
682
|
+
: sql<number>`CASE
|
|
683
|
+
WHEN ${posts.status} = 'draft' THEN ${posts.updatedAt}
|
|
684
|
+
ELSE ${posts.lastActivityAt}
|
|
685
|
+
END`;
|
|
686
|
+
const pinnedSortExpr = sql<number>`coalesce(${posts.pinnedAt}, -1)`;
|
|
687
|
+
const featuredSortExpr = sql<number>`coalesce(${posts.featuredAt}, -1)`;
|
|
688
|
+
const sortTimestampSortExpr = sql<number>`coalesce(${sortTimestampExpr}, -1)`;
|
|
689
|
+
const ratingPresenceExpr = sql<number>`CASE
|
|
690
|
+
WHEN ${posts.rating} IS NULL THEN 0
|
|
691
|
+
ELSE 1
|
|
692
|
+
END`;
|
|
693
|
+
const ratingSortExpr = sql<number>`coalesce(${posts.rating}, -1)`;
|
|
694
|
+
const cursorPinnedAt = cursorPost.pinnedAt ?? -1;
|
|
695
|
+
const cursorFeaturedAt = cursorPost.featuredAt ?? -1;
|
|
696
|
+
const cursorSortTimestamp = getCursorSortTimestamp(cursorPost);
|
|
697
|
+
const cursorRatingPresence = cursorPost.rating === null ? 0 : 1;
|
|
698
|
+
const cursorRating = cursorPost.rating ?? -1;
|
|
699
|
+
|
|
700
|
+
if (filters.featured || filters.sortOrder === undefined) {
|
|
701
|
+
if (filters.featured) {
|
|
702
|
+
return buildLexicographicCursorCondition([
|
|
703
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
704
|
+
{
|
|
705
|
+
direction: "desc",
|
|
706
|
+
expr: featuredSortExpr,
|
|
707
|
+
value: cursorFeaturedAt,
|
|
708
|
+
},
|
|
709
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return buildLexicographicCursorCondition([
|
|
714
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
715
|
+
{
|
|
716
|
+
direction: "desc",
|
|
717
|
+
expr: sortTimestampSortExpr,
|
|
718
|
+
value: cursorSortTimestamp,
|
|
719
|
+
},
|
|
720
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
721
|
+
]);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (filters.sortOrder === "oldest") {
|
|
725
|
+
return buildLexicographicCursorCondition([
|
|
726
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
727
|
+
{
|
|
728
|
+
direction: "asc",
|
|
729
|
+
expr: sortTimestampSortExpr,
|
|
730
|
+
value: cursorSortTimestamp,
|
|
731
|
+
},
|
|
732
|
+
{ direction: "asc", expr: posts.id, value: cursorPost.id },
|
|
733
|
+
]);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (filters.sortOrder === "rating_desc") {
|
|
737
|
+
return buildLexicographicCursorCondition([
|
|
738
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
739
|
+
{
|
|
740
|
+
direction: "desc",
|
|
741
|
+
expr: ratingPresenceExpr,
|
|
742
|
+
value: cursorRatingPresence,
|
|
743
|
+
},
|
|
744
|
+
{ direction: "desc", expr: ratingSortExpr, value: cursorRating },
|
|
745
|
+
{
|
|
746
|
+
direction: "desc",
|
|
747
|
+
expr: sortTimestampSortExpr,
|
|
748
|
+
value: cursorSortTimestamp,
|
|
749
|
+
},
|
|
750
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
751
|
+
]);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return buildLexicographicCursorCondition([
|
|
755
|
+
{ direction: "desc", expr: pinnedSortExpr, value: cursorPinnedAt },
|
|
756
|
+
{
|
|
757
|
+
direction: "desc",
|
|
758
|
+
expr: ratingPresenceExpr,
|
|
759
|
+
value: cursorRatingPresence,
|
|
760
|
+
},
|
|
761
|
+
{ direction: "asc", expr: ratingSortExpr, value: cursorRating },
|
|
762
|
+
{
|
|
763
|
+
direction: "desc",
|
|
764
|
+
expr: sortTimestampSortExpr,
|
|
765
|
+
value: cursorSortTimestamp,
|
|
766
|
+
},
|
|
767
|
+
{ direction: "desc", expr: posts.id, value: cursorPost.id },
|
|
768
|
+
]);
|
|
769
|
+
}
|
|
770
|
+
|
|
325
771
|
function toPost(
|
|
326
772
|
row: typeof posts.$inferSelect,
|
|
327
773
|
slug: string,
|
|
328
|
-
visibility:
|
|
774
|
+
visibility: string,
|
|
329
775
|
): Post {
|
|
330
776
|
return {
|
|
331
777
|
id: row.id,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
778
|
+
siteId: row.siteId,
|
|
779
|
+
format: ensurePostFormat(row.format, Error),
|
|
780
|
+
status: ensurePostStatus(row.status, Error),
|
|
781
|
+
visibility: ensurePostVisibility(visibility, Error),
|
|
335
782
|
pinnedAt: row.pinnedAt,
|
|
336
783
|
featuredAt: row.featuredAt,
|
|
337
784
|
slug,
|
|
@@ -342,7 +789,7 @@ export function createPostService(
|
|
|
342
789
|
bodyText: row.bodyText,
|
|
343
790
|
quoteText: row.quoteText,
|
|
344
791
|
summary: row.summary,
|
|
345
|
-
rating: row.rating,
|
|
792
|
+
rating: ensurePostRating(row.rating, Error),
|
|
346
793
|
replyToId: row.replyToId,
|
|
347
794
|
threadId: row.threadId,
|
|
348
795
|
deletedAt: row.deletedAt,
|
|
@@ -357,19 +804,21 @@ export function createPostService(
|
|
|
357
804
|
row: typeof posts.$inferSelect | undefined,
|
|
358
805
|
): Promise<Post | null> {
|
|
359
806
|
if (!row) return null;
|
|
360
|
-
const slug = await
|
|
807
|
+
const slug = await resolvedPaths.getPostSlug(row.id);
|
|
361
808
|
if (!slug) return null;
|
|
362
809
|
const rootVisibilityMap = await getThreadVisibilityMap([row.threadId]);
|
|
363
810
|
const visibility = rootVisibilityMap.get(row.threadId) ?? row.visibility;
|
|
364
811
|
if (!visibility) return null;
|
|
365
|
-
return toPost(row, slug, visibility
|
|
812
|
+
return toPost(row, slug, visibility);
|
|
366
813
|
}
|
|
367
814
|
|
|
368
815
|
async function hydratePosts(
|
|
369
816
|
rows: (typeof posts.$inferSelect)[],
|
|
370
817
|
): Promise<Post[]> {
|
|
371
818
|
if (rows.length === 0) return [];
|
|
372
|
-
const slugMap = await
|
|
819
|
+
const slugMap = await resolvedPaths.getPostSlugMap(
|
|
820
|
+
rows.map((row) => row.id),
|
|
821
|
+
);
|
|
373
822
|
const rootVisibilityMap = await getThreadVisibilityMap(
|
|
374
823
|
rows.map((row) => row.threadId),
|
|
375
824
|
);
|
|
@@ -378,13 +827,40 @@ export function createPostService(
|
|
|
378
827
|
const slug = slugMap.get(row.id);
|
|
379
828
|
const visibility =
|
|
380
829
|
rootVisibilityMap.get(row.threadId) ?? row.visibility;
|
|
381
|
-
return slug && visibility
|
|
382
|
-
? toPost(row, slug, visibility as Visibility)
|
|
383
|
-
: null;
|
|
830
|
+
return slug && visibility ? toPost(row, slug, visibility) : null;
|
|
384
831
|
})
|
|
385
832
|
.filter((row): row is Post => row !== null);
|
|
386
833
|
}
|
|
387
834
|
|
|
835
|
+
async function hydratePostsById(ids: string[]): Promise<Map<string, Post>> {
|
|
836
|
+
const result = new Map<string, Post>();
|
|
837
|
+
const uniqueIds = [...new Set(ids)];
|
|
838
|
+
|
|
839
|
+
if (uniqueIds.length === 0) {
|
|
840
|
+
return result;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const rows = await batchQueryRows(uniqueIds, (chunk) =>
|
|
844
|
+
db
|
|
845
|
+
.select()
|
|
846
|
+
.from(posts)
|
|
847
|
+
.where(
|
|
848
|
+
and(
|
|
849
|
+
eq(posts.siteId, siteId),
|
|
850
|
+
inArray(posts.id, chunk),
|
|
851
|
+
eq(posts.status, "published"),
|
|
852
|
+
isNull(posts.deletedAt),
|
|
853
|
+
),
|
|
854
|
+
),
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
for (const post of await hydratePosts(rows)) {
|
|
858
|
+
result.set(post.id, post);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return result;
|
|
862
|
+
}
|
|
863
|
+
|
|
388
864
|
async function getThreadVisibilityMap(
|
|
389
865
|
threadIds: string[],
|
|
390
866
|
): Promise<Map<string, Visibility>> {
|
|
@@ -396,38 +872,188 @@ export function createPostService(
|
|
|
396
872
|
db
|
|
397
873
|
.select({ id: posts.id, visibility: posts.visibility })
|
|
398
874
|
.from(posts)
|
|
399
|
-
.where(inArray(posts.id, chunk)),
|
|
875
|
+
.where(and(eq(posts.siteId, siteId), inArray(posts.id, chunk))),
|
|
400
876
|
);
|
|
401
877
|
|
|
402
878
|
for (const row of rows) {
|
|
403
879
|
if (row.visibility) {
|
|
404
|
-
result.set(row.id, row.visibility
|
|
880
|
+
result.set(row.id, ensurePostVisibility(row.visibility, Error));
|
|
405
881
|
}
|
|
406
882
|
}
|
|
407
883
|
|
|
408
884
|
return result;
|
|
409
885
|
}
|
|
410
886
|
|
|
887
|
+
function buildThreadRootPageConditions(options?: ThreadRootPageOptions) {
|
|
888
|
+
const conditions = [isNull(posts.deletedAt)];
|
|
889
|
+
const status = options?.status;
|
|
890
|
+
|
|
891
|
+
if (status) {
|
|
892
|
+
conditions.push(eq(posts.status, status));
|
|
893
|
+
}
|
|
894
|
+
if (options?.excludePrivate) {
|
|
895
|
+
conditions.push(sql`${effectiveVisibilityExpr} != 'private'`);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return conditions;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function isMediaAttachmentInput(
|
|
902
|
+
attachment: PostAttachmentInput,
|
|
903
|
+
): attachment is Extract<PostAttachmentInput, { type: "media" }> {
|
|
904
|
+
return attachment.type === "media";
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function createAttachmentMediaIds(
|
|
908
|
+
attachments: PostAttachmentInput[],
|
|
909
|
+
deps: PostAttachmentDeps,
|
|
910
|
+
) {
|
|
911
|
+
if (attachments.length > MAX_MEDIA_ATTACHMENTS) {
|
|
912
|
+
throw new ValidationError(
|
|
913
|
+
`Posts allow at most ${MAX_MEDIA_ATTACHMENTS} attachments`,
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const orderedMediaIds: string[] = [];
|
|
918
|
+
const createdTextMediaIds: string[] = [];
|
|
919
|
+
const referencedMediaIds = attachments
|
|
920
|
+
.filter(isMediaAttachmentInput)
|
|
921
|
+
.map((attachment) => attachment.mediaId);
|
|
922
|
+
|
|
923
|
+
await deps.media.validateIds(referencedMediaIds);
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
for (const attachment of attachments) {
|
|
927
|
+
if (isMediaAttachmentInput(attachment)) {
|
|
928
|
+
orderedMediaIds.push(attachment.mediaId);
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const created = await deps.media.createTextAttachment(attachment, {
|
|
933
|
+
storage: deps.storage,
|
|
934
|
+
storageDriver: deps.storageDriver,
|
|
935
|
+
maxFileSizeMB: deps.maxFileSizeMB,
|
|
936
|
+
});
|
|
937
|
+
orderedMediaIds.push(created.id);
|
|
938
|
+
createdTextMediaIds.push(created.id);
|
|
939
|
+
}
|
|
940
|
+
} catch (error) {
|
|
941
|
+
await cleanupCreatedTextAttachments(createdTextMediaIds, deps);
|
|
942
|
+
throw error;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return { orderedMediaIds, createdTextMediaIds };
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
async function applyAttachmentAltUpdates(
|
|
949
|
+
attachments: PostAttachmentInput[],
|
|
950
|
+
deps: PostAttachmentDeps,
|
|
951
|
+
) {
|
|
952
|
+
const altUpdates = attachments
|
|
953
|
+
.filter(isMediaAttachmentInput)
|
|
954
|
+
.filter((attachment) => attachment.alt !== undefined)
|
|
955
|
+
.map((attachment) =>
|
|
956
|
+
deps.media.updateAlt(attachment.mediaId, attachment.alt ?? ""),
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
await Promise.all(altUpdates);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function cleanupCreatedTextAttachments(
|
|
963
|
+
mediaIds: string[],
|
|
964
|
+
deps: PostAttachmentDeps,
|
|
965
|
+
) {
|
|
966
|
+
if (mediaIds.length === 0) return;
|
|
967
|
+
await deps.media.deleteByIds(mediaIds, deps.storage).catch(() => undefined);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async function getCollectionIdsForPost(postId: string): Promise<string[]> {
|
|
971
|
+
const rows = await db
|
|
972
|
+
.select({ collectionId: postCollections.collectionId })
|
|
973
|
+
.from(postCollections)
|
|
974
|
+
.where(
|
|
975
|
+
and(
|
|
976
|
+
eq(postCollections.siteId, siteId),
|
|
977
|
+
eq(postCollections.postId, postId),
|
|
978
|
+
),
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
return rows.map((row) => row.collectionId);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function buildRollbackUpdate(
|
|
985
|
+
post: Post,
|
|
986
|
+
collectionIds: string[],
|
|
987
|
+
): UpdatePost {
|
|
988
|
+
return {
|
|
989
|
+
format: post.format,
|
|
990
|
+
title: post.title,
|
|
991
|
+
body: post.body ?? null,
|
|
992
|
+
slug: post.slug,
|
|
993
|
+
status: post.status,
|
|
994
|
+
visibility: post.visibility,
|
|
995
|
+
pinned: post.pinnedAt !== null,
|
|
996
|
+
featured: post.featuredAt !== null,
|
|
997
|
+
url: post.url,
|
|
998
|
+
quoteText: post.quoteText,
|
|
999
|
+
rating: post.rating,
|
|
1000
|
+
collectionIds,
|
|
1001
|
+
publishedAt: post.publishedAt ?? undefined,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
411
1005
|
return {
|
|
412
1006
|
async getById(id) {
|
|
413
1007
|
const result = await db
|
|
414
1008
|
.select()
|
|
415
1009
|
.from(posts)
|
|
416
|
-
.where(
|
|
1010
|
+
.where(
|
|
1011
|
+
and(
|
|
1012
|
+
eq(posts.siteId, siteId),
|
|
1013
|
+
eq(posts.id, id),
|
|
1014
|
+
isNull(posts.deletedAt),
|
|
1015
|
+
),
|
|
1016
|
+
)
|
|
417
1017
|
.limit(1);
|
|
418
1018
|
return hydratePost(result[0]);
|
|
419
1019
|
},
|
|
420
1020
|
|
|
421
1021
|
async getBySlug(slug) {
|
|
422
|
-
const resolved = await
|
|
1022
|
+
const resolved = await resolvedPaths.resolve(slug);
|
|
423
1023
|
if (!resolved || resolved.kind !== "slug" || !resolved.postId) {
|
|
424
1024
|
return null;
|
|
425
1025
|
}
|
|
426
1026
|
return this.getById(resolved.postId);
|
|
427
1027
|
},
|
|
428
1028
|
|
|
1029
|
+
async suggestSlug(input) {
|
|
1030
|
+
return generatePostSlug({
|
|
1031
|
+
slug: input.slug,
|
|
1032
|
+
title: input.title,
|
|
1033
|
+
idLength: config.slugIdLength,
|
|
1034
|
+
isAvailable: (candidate) =>
|
|
1035
|
+
isSlugAvailableForPost(candidate, input.excludePostId),
|
|
1036
|
+
});
|
|
1037
|
+
},
|
|
1038
|
+
|
|
1039
|
+
async checkSlugAvailability(slug, excludePostId) {
|
|
1040
|
+
const issue = getSlugValidationIssue(slug);
|
|
1041
|
+
if (issue === "invalid") {
|
|
1042
|
+
throw new ValidationError("Slug contains invalid characters");
|
|
1043
|
+
}
|
|
1044
|
+
if (issue === "reserved") {
|
|
1045
|
+
throw new ValidationError("Slug is reserved");
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return isSlugAvailableForPost(slug, excludePostId);
|
|
1049
|
+
},
|
|
1050
|
+
|
|
429
1051
|
async list(filters = {}) {
|
|
430
1052
|
const conditions = buildFilterConditions(filters);
|
|
1053
|
+
const cursorCondition = await buildListCursorCondition(filters);
|
|
1054
|
+
if (filters.cursor && !cursorCondition) {
|
|
1055
|
+
return [];
|
|
1056
|
+
}
|
|
431
1057
|
const sortTimestamp =
|
|
432
1058
|
filters.status === "draft"
|
|
433
1059
|
? posts.updatedAt
|
|
@@ -438,21 +1064,50 @@ export function createPostService(
|
|
|
438
1064
|
ELSE ${posts.lastActivityAt}
|
|
439
1065
|
END`;
|
|
440
1066
|
|
|
441
|
-
if (
|
|
442
|
-
conditions.push(
|
|
1067
|
+
if (cursorCondition) {
|
|
1068
|
+
conditions.push(cursorCondition);
|
|
443
1069
|
}
|
|
444
1070
|
|
|
445
|
-
|
|
1071
|
+
const ratingPresence = sql<number>`CASE
|
|
1072
|
+
WHEN ${posts.rating} IS NULL THEN 0
|
|
1073
|
+
ELSE 1
|
|
1074
|
+
END`;
|
|
1075
|
+
|
|
1076
|
+
const baseQuery = db
|
|
446
1077
|
.select()
|
|
447
1078
|
.from(posts)
|
|
448
1079
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
449
|
-
.orderBy(
|
|
450
|
-
desc(posts.pinnedAt),
|
|
451
|
-
filters.featured ? desc(posts.featuredAt) : desc(sortTimestamp),
|
|
452
|
-
desc(posts.id),
|
|
453
|
-
)
|
|
454
1080
|
.limit(filters.limit ?? 100);
|
|
455
1081
|
|
|
1082
|
+
let query =
|
|
1083
|
+
filters.featured || filters.sortOrder === undefined
|
|
1084
|
+
? baseQuery.orderBy(
|
|
1085
|
+
desc(posts.pinnedAt),
|
|
1086
|
+
filters.featured ? desc(posts.featuredAt) : desc(sortTimestamp),
|
|
1087
|
+
desc(posts.id),
|
|
1088
|
+
)
|
|
1089
|
+
: filters.sortOrder === "oldest"
|
|
1090
|
+
? baseQuery.orderBy(
|
|
1091
|
+
desc(posts.pinnedAt),
|
|
1092
|
+
asc(sortTimestamp),
|
|
1093
|
+
asc(posts.id),
|
|
1094
|
+
)
|
|
1095
|
+
: filters.sortOrder === "rating_desc"
|
|
1096
|
+
? baseQuery.orderBy(
|
|
1097
|
+
desc(posts.pinnedAt),
|
|
1098
|
+
desc(ratingPresence),
|
|
1099
|
+
desc(posts.rating),
|
|
1100
|
+
desc(sortTimestamp),
|
|
1101
|
+
desc(posts.id),
|
|
1102
|
+
)
|
|
1103
|
+
: baseQuery.orderBy(
|
|
1104
|
+
desc(posts.pinnedAt),
|
|
1105
|
+
desc(ratingPresence),
|
|
1106
|
+
asc(posts.rating),
|
|
1107
|
+
desc(sortTimestamp),
|
|
1108
|
+
desc(posts.id),
|
|
1109
|
+
);
|
|
1110
|
+
|
|
456
1111
|
if (filters.offset !== undefined) {
|
|
457
1112
|
query = query.offset(filters.offset) as typeof query;
|
|
458
1113
|
}
|
|
@@ -472,12 +1127,55 @@ export function createPostService(
|
|
|
472
1127
|
return result[0]?.count ?? 0;
|
|
473
1128
|
},
|
|
474
1129
|
|
|
1130
|
+
async countUpTo(filters = {}, limit) {
|
|
1131
|
+
const normalizedLimit = Math.max(0, Math.trunc(limit));
|
|
1132
|
+
if (normalizedLimit === 0) {
|
|
1133
|
+
return 0;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const conditions = buildFilterConditions(filters);
|
|
1137
|
+
const rows = await db
|
|
1138
|
+
.select({ id: posts.id })
|
|
1139
|
+
.from(posts)
|
|
1140
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1141
|
+
.limit(normalizedLimit);
|
|
1142
|
+
|
|
1143
|
+
return rows.length;
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1146
|
+
async countByYearMonth(filters = {}) {
|
|
1147
|
+
const conditions = [
|
|
1148
|
+
...buildFilterConditions(filters),
|
|
1149
|
+
isNotNull(posts.publishedAt),
|
|
1150
|
+
];
|
|
1151
|
+
const publishedYearMonthExpr = buildPublishedYearMonthExpr();
|
|
1152
|
+
|
|
1153
|
+
return db
|
|
1154
|
+
.select({
|
|
1155
|
+
yearMonth: publishedYearMonthExpr.as("year_month"),
|
|
1156
|
+
count: sql<number>`count(*)`.as("count"),
|
|
1157
|
+
})
|
|
1158
|
+
.from(posts)
|
|
1159
|
+
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1160
|
+
.groupBy(publishedYearMonthExpr)
|
|
1161
|
+
.orderBy(desc(publishedYearMonthExpr));
|
|
1162
|
+
},
|
|
1163
|
+
|
|
475
1164
|
async create(data, summaryConfig) {
|
|
476
|
-
const id =
|
|
1165
|
+
const id = createEntityId("post");
|
|
477
1166
|
const timestamp = now();
|
|
1167
|
+
const format = ensurePostFormat(data.format);
|
|
1168
|
+
const requestedStatus =
|
|
1169
|
+
data.status !== undefined ? ensurePostStatus(data.status) : undefined;
|
|
1170
|
+
const requestedVisibility =
|
|
1171
|
+
data.visibility !== undefined
|
|
1172
|
+
? ensurePostVisibility(data.visibility)
|
|
1173
|
+
: undefined;
|
|
1174
|
+
const rating = ensurePostRating(data.rating);
|
|
478
1175
|
|
|
479
1176
|
assertPostFormatShape({
|
|
480
|
-
format
|
|
1177
|
+
format,
|
|
1178
|
+
title: data.title,
|
|
481
1179
|
url: data.url,
|
|
482
1180
|
quoteText: data.quoteText,
|
|
483
1181
|
});
|
|
@@ -490,7 +1188,7 @@ export function createPostService(
|
|
|
490
1188
|
|
|
491
1189
|
// Generate summary for titled notes with body content
|
|
492
1190
|
let summary: string | null = null;
|
|
493
|
-
if (
|
|
1191
|
+
if (format === "note" && data.title && body && summaryConfig) {
|
|
494
1192
|
summary = extractSummary(
|
|
495
1193
|
body,
|
|
496
1194
|
summaryConfig.maxParagraphs,
|
|
@@ -500,8 +1198,8 @@ export function createPostService(
|
|
|
500
1198
|
|
|
501
1199
|
// Handle thread relationship
|
|
502
1200
|
let threadId = id;
|
|
503
|
-
let status: Status =
|
|
504
|
-
let visibility: Visibility | null =
|
|
1201
|
+
let status: Status = requestedStatus ?? "published";
|
|
1202
|
+
let visibility: Visibility | null = requestedVisibility ?? "public";
|
|
505
1203
|
|
|
506
1204
|
if (data.replyToId) {
|
|
507
1205
|
const parent = await this.getById(data.replyToId);
|
|
@@ -524,7 +1222,7 @@ export function createPostService(
|
|
|
524
1222
|
: await this.getById(parent.threadId);
|
|
525
1223
|
if (root) {
|
|
526
1224
|
if (data.status !== "draft") {
|
|
527
|
-
status = root.status
|
|
1225
|
+
status = root.status;
|
|
528
1226
|
}
|
|
529
1227
|
}
|
|
530
1228
|
visibility = null;
|
|
@@ -557,7 +1255,7 @@ export function createPostService(
|
|
|
557
1255
|
isAvailable: isSlugAvailable,
|
|
558
1256
|
});
|
|
559
1257
|
// Verify the alias path is available before proceeding
|
|
560
|
-
if (!(await
|
|
1258
|
+
if (!(await resolvedPaths.isPathAvailable(normalized))) {
|
|
561
1259
|
throw new ConflictError(`Path "${normalized}" is already in use`);
|
|
562
1260
|
}
|
|
563
1261
|
aliasPath = normalized;
|
|
@@ -574,48 +1272,41 @@ export function createPostService(
|
|
|
574
1272
|
const collectionIds = [...new Set(data.collectionIds ?? [])];
|
|
575
1273
|
|
|
576
1274
|
try {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
redirectToPath: null,
|
|
607
|
-
redirectType: null,
|
|
608
|
-
createdAt: timestamp,
|
|
609
|
-
updatedAt: timestamp,
|
|
610
|
-
}),
|
|
611
|
-
];
|
|
612
|
-
|
|
613
|
-
if (aliasPath) {
|
|
1275
|
+
if (usesBatchWrites) {
|
|
1276
|
+
const writeQueries = [];
|
|
1277
|
+
|
|
1278
|
+
writeQueries.push(
|
|
1279
|
+
db.insert(posts).values({
|
|
1280
|
+
id,
|
|
1281
|
+
siteId,
|
|
1282
|
+
format,
|
|
1283
|
+
status,
|
|
1284
|
+
visibility,
|
|
1285
|
+
pinnedAt: data.pinned ? timestamp : null,
|
|
1286
|
+
featuredAt: data.featured ? timestamp : null,
|
|
1287
|
+
title: data.title ?? null,
|
|
1288
|
+
url: data.url ?? null,
|
|
1289
|
+
body: body ?? null,
|
|
1290
|
+
bodyHtml,
|
|
1291
|
+
bodyText,
|
|
1292
|
+
quoteText: data.quoteText ?? null,
|
|
1293
|
+
summary,
|
|
1294
|
+
rating,
|
|
1295
|
+
replyToId: data.replyToId ?? null,
|
|
1296
|
+
threadId,
|
|
1297
|
+
publishedAt,
|
|
1298
|
+
lastActivityAt: publishedAt ?? timestamp,
|
|
1299
|
+
createdAt: timestamp,
|
|
1300
|
+
updatedAt: timestamp,
|
|
1301
|
+
}),
|
|
1302
|
+
);
|
|
1303
|
+
|
|
614
1304
|
writeQueries.push(
|
|
615
1305
|
db.insert(pathRegistry).values({
|
|
616
|
-
id:
|
|
617
|
-
|
|
618
|
-
|
|
1306
|
+
id: createEntityId("path"),
|
|
1307
|
+
siteId,
|
|
1308
|
+
path: normalizePath(slug),
|
|
1309
|
+
kind: "slug",
|
|
619
1310
|
postId: id,
|
|
620
1311
|
collectionId: null,
|
|
621
1312
|
redirectToPath: null,
|
|
@@ -624,26 +1315,109 @@ export function createPostService(
|
|
|
624
1315
|
updatedAt: timestamp,
|
|
625
1316
|
}),
|
|
626
1317
|
);
|
|
627
|
-
}
|
|
628
1318
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1319
|
+
if (aliasPath) {
|
|
1320
|
+
writeQueries.push(
|
|
1321
|
+
db.insert(pathRegistry).values({
|
|
1322
|
+
id: createEntityId("path"),
|
|
1323
|
+
siteId,
|
|
1324
|
+
path: normalizePath(aliasPath),
|
|
1325
|
+
kind: "alias",
|
|
633
1326
|
postId: id,
|
|
634
|
-
collectionId,
|
|
1327
|
+
collectionId: null,
|
|
1328
|
+
redirectToPath: null,
|
|
1329
|
+
redirectType: null,
|
|
635
1330
|
createdAt: timestamp,
|
|
636
|
-
|
|
637
|
-
|
|
1331
|
+
updatedAt: timestamp,
|
|
1332
|
+
}),
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (collectionIds.length > 0) {
|
|
1337
|
+
writeQueries.push(
|
|
1338
|
+
db.insert(postCollections).values(
|
|
1339
|
+
collectionIds.map((collectionId) => ({
|
|
1340
|
+
siteId,
|
|
1341
|
+
postId: id,
|
|
1342
|
+
collectionId,
|
|
1343
|
+
createdAt: timestamp,
|
|
1344
|
+
})),
|
|
1345
|
+
),
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
await db.batch(
|
|
1350
|
+
writeQueries as [
|
|
1351
|
+
(typeof writeQueries)[number],
|
|
1352
|
+
...(typeof writeQueries)[number][],
|
|
1353
|
+
],
|
|
638
1354
|
);
|
|
639
|
-
}
|
|
1355
|
+
} else {
|
|
1356
|
+
await db.transaction(async (tx) => {
|
|
1357
|
+
await tx.insert(posts).values({
|
|
1358
|
+
id,
|
|
1359
|
+
siteId,
|
|
1360
|
+
format,
|
|
1361
|
+
status,
|
|
1362
|
+
visibility,
|
|
1363
|
+
pinnedAt: data.pinned ? timestamp : null,
|
|
1364
|
+
featuredAt: data.featured ? timestamp : null,
|
|
1365
|
+
title: data.title ?? null,
|
|
1366
|
+
url: data.url ?? null,
|
|
1367
|
+
body: body ?? null,
|
|
1368
|
+
bodyHtml,
|
|
1369
|
+
bodyText,
|
|
1370
|
+
quoteText: data.quoteText ?? null,
|
|
1371
|
+
summary,
|
|
1372
|
+
rating,
|
|
1373
|
+
replyToId: data.replyToId ?? null,
|
|
1374
|
+
threadId,
|
|
1375
|
+
publishedAt,
|
|
1376
|
+
lastActivityAt: publishedAt ?? timestamp,
|
|
1377
|
+
createdAt: timestamp,
|
|
1378
|
+
updatedAt: timestamp,
|
|
1379
|
+
});
|
|
640
1380
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
1381
|
+
await tx.insert(pathRegistry).values({
|
|
1382
|
+
id: createEntityId("path"),
|
|
1383
|
+
siteId,
|
|
1384
|
+
path: normalizePath(slug),
|
|
1385
|
+
kind: "slug",
|
|
1386
|
+
postId: id,
|
|
1387
|
+
collectionId: null,
|
|
1388
|
+
redirectToPath: null,
|
|
1389
|
+
redirectType: null,
|
|
1390
|
+
createdAt: timestamp,
|
|
1391
|
+
updatedAt: timestamp,
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
if (aliasPath) {
|
|
1395
|
+
await tx.insert(pathRegistry).values({
|
|
1396
|
+
id: createEntityId("path"),
|
|
1397
|
+
siteId,
|
|
1398
|
+
path: normalizePath(aliasPath),
|
|
1399
|
+
kind: "alias",
|
|
1400
|
+
postId: id,
|
|
1401
|
+
collectionId: null,
|
|
1402
|
+
redirectToPath: null,
|
|
1403
|
+
redirectType: null,
|
|
1404
|
+
createdAt: timestamp,
|
|
1405
|
+
updatedAt: timestamp,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (collectionIds.length > 0) {
|
|
1410
|
+
await tx.insert(postCollections).values(
|
|
1411
|
+
collectionIds.map((collectionId) => ({
|
|
1412
|
+
siteId,
|
|
1413
|
+
postId: id,
|
|
1414
|
+
collectionId,
|
|
1415
|
+
createdAt: timestamp,
|
|
1416
|
+
})),
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
647
1421
|
} catch (err) {
|
|
648
1422
|
if (err instanceof ConflictError) {
|
|
649
1423
|
throw new ConflictError(`Slug "${slug}" is already in use`);
|
|
@@ -667,19 +1441,61 @@ export function createPostService(
|
|
|
667
1441
|
return post;
|
|
668
1442
|
},
|
|
669
1443
|
|
|
1444
|
+
async createWithAttachments(data, attachments, deps, summaryConfig) {
|
|
1445
|
+
const attachmentInputs = attachments ?? [];
|
|
1446
|
+
const { orderedMediaIds, createdTextMediaIds } =
|
|
1447
|
+
await createAttachmentMediaIds(attachmentInputs, deps);
|
|
1448
|
+
|
|
1449
|
+
try {
|
|
1450
|
+
const post = await this.create(data, summaryConfig);
|
|
1451
|
+
|
|
1452
|
+
try {
|
|
1453
|
+
if (orderedMediaIds.length > 0) {
|
|
1454
|
+
await deps.media.attachToPost(post.id, orderedMediaIds);
|
|
1455
|
+
}
|
|
1456
|
+
await applyAttachmentAltUpdates(attachmentInputs, deps);
|
|
1457
|
+
return post;
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
await deps.media.attachToPost(post.id, []).catch(() => undefined);
|
|
1460
|
+
await this.delete(post.id, {
|
|
1461
|
+
media: deps.media,
|
|
1462
|
+
storage: deps.storage,
|
|
1463
|
+
}).catch(() => undefined);
|
|
1464
|
+
await cleanupCreatedTextAttachments(createdTextMediaIds, deps);
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
await cleanupCreatedTextAttachments(createdTextMediaIds, deps);
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
|
|
670
1473
|
async update(id, data, summaryConfig) {
|
|
671
1474
|
const existing = await this.getById(id);
|
|
672
1475
|
if (!existing) return null;
|
|
673
1476
|
|
|
674
1477
|
const timestamp = now();
|
|
675
|
-
const nextFormat =
|
|
1478
|
+
const nextFormat =
|
|
1479
|
+
data.format !== undefined
|
|
1480
|
+
? ensurePostFormat(data.format)
|
|
1481
|
+
: existing.format;
|
|
676
1482
|
const nextUrl = data.url !== undefined ? data.url : existing.url;
|
|
677
1483
|
const nextQuoteText =
|
|
678
1484
|
data.quoteText !== undefined ? data.quoteText : existing.quoteText;
|
|
679
|
-
const nextStatus =
|
|
1485
|
+
const nextStatus =
|
|
1486
|
+
data.status !== undefined
|
|
1487
|
+
? ensurePostStatus(data.status)
|
|
1488
|
+
: existing.status;
|
|
1489
|
+
const nextVisibility =
|
|
1490
|
+
data.visibility !== undefined
|
|
1491
|
+
? ensurePostVisibility(data.visibility)
|
|
1492
|
+
: undefined;
|
|
1493
|
+
const nextRating =
|
|
1494
|
+
data.rating !== undefined ? ensurePostRating(data.rating) : undefined;
|
|
680
1495
|
|
|
681
1496
|
assertPostFormatShape({
|
|
682
1497
|
format: nextFormat,
|
|
1498
|
+
title: data.title !== undefined ? data.title : existing.title,
|
|
683
1499
|
url: nextUrl,
|
|
684
1500
|
quoteText: nextQuoteText,
|
|
685
1501
|
});
|
|
@@ -694,7 +1510,7 @@ export function createPostService(
|
|
|
694
1510
|
data.slug !== undefined && data.slug !== existing.slug;
|
|
695
1511
|
if (slugChanged && data.slug) {
|
|
696
1512
|
try {
|
|
697
|
-
await
|
|
1513
|
+
await resolvedPaths.updatePostSlug(id, data.slug);
|
|
698
1514
|
} catch (err) {
|
|
699
1515
|
if (err instanceof ConflictError) {
|
|
700
1516
|
throw new ConflictError(`Slug "${data.slug}" is already in use`);
|
|
@@ -703,11 +1519,11 @@ export function createPostService(
|
|
|
703
1519
|
}
|
|
704
1520
|
}
|
|
705
1521
|
|
|
706
|
-
if (data.format !== undefined) updates.format =
|
|
1522
|
+
if (data.format !== undefined) updates.format = nextFormat;
|
|
707
1523
|
if (data.title !== undefined) updates.title = data.title;
|
|
708
1524
|
if (data.url !== undefined) updates.url = data.url;
|
|
709
1525
|
if (data.quoteText !== undefined) updates.quoteText = data.quoteText;
|
|
710
|
-
if (data.rating !== undefined) updates.rating =
|
|
1526
|
+
if (data.rating !== undefined) updates.rating = nextRating;
|
|
711
1527
|
if (data.pinned !== undefined)
|
|
712
1528
|
updates.pinnedAt = data.pinned ? now() : null;
|
|
713
1529
|
if (data.featured !== undefined)
|
|
@@ -728,7 +1544,7 @@ export function createPostService(
|
|
|
728
1544
|
|
|
729
1545
|
// Recompute summary when body, title, or format change
|
|
730
1546
|
if (summaryConfig) {
|
|
731
|
-
const format =
|
|
1547
|
+
const format = nextFormat;
|
|
732
1548
|
const title = data.title !== undefined ? data.title : existing.title;
|
|
733
1549
|
const body =
|
|
734
1550
|
data.bodyMarkdown !== undefined
|
|
@@ -767,8 +1583,7 @@ export function createPostService(
|
|
|
767
1583
|
const statusChanged =
|
|
768
1584
|
data.status !== undefined && data.status !== existing.status;
|
|
769
1585
|
const visibilityChanged =
|
|
770
|
-
|
|
771
|
-
data.visibility !== existing.visibility;
|
|
1586
|
+
nextVisibility !== undefined && nextVisibility !== existing.visibility;
|
|
772
1587
|
const publishedAtChanged = data.publishedAt !== undefined;
|
|
773
1588
|
const nextPublishedAt =
|
|
774
1589
|
nextStatus === "draft"
|
|
@@ -779,9 +1594,9 @@ export function createPostService(
|
|
|
779
1594
|
? timestamp
|
|
780
1595
|
: (existing.publishedAt ?? timestamp);
|
|
781
1596
|
|
|
782
|
-
if (statusChanged) updates.status =
|
|
1597
|
+
if (statusChanged) updates.status = nextStatus;
|
|
783
1598
|
if (visibilityChanged && !isThreadReply(existing)) {
|
|
784
|
-
updates.visibility =
|
|
1599
|
+
updates.visibility = nextVisibility;
|
|
785
1600
|
}
|
|
786
1601
|
if (statusChanged || publishedAtChanged || existing.status === "draft") {
|
|
787
1602
|
updates.publishedAt = nextPublishedAt;
|
|
@@ -793,6 +1608,9 @@ export function createPostService(
|
|
|
793
1608
|
const needsReplyVisibilityCleanup =
|
|
794
1609
|
!isThreadReply(existing) && (statusChanged || visibilityChanged);
|
|
795
1610
|
const needsCollectionSync = data.collectionIds !== undefined;
|
|
1611
|
+
const nextCollectionIds = needsCollectionSync
|
|
1612
|
+
? [...new Set(data.collectionIds ?? [])]
|
|
1613
|
+
: [];
|
|
796
1614
|
const needsThreadActivityRecalc =
|
|
797
1615
|
statusChanged || publishedAtChanged || existing.status === "draft";
|
|
798
1616
|
const hasExtraWrites =
|
|
@@ -803,7 +1621,7 @@ export function createPostService(
|
|
|
803
1621
|
const result = await db
|
|
804
1622
|
.update(posts)
|
|
805
1623
|
.set(updates)
|
|
806
|
-
.where(eq(posts.id, id))
|
|
1624
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, id)))
|
|
807
1625
|
.returning();
|
|
808
1626
|
if (needsThreadActivityRecalc) {
|
|
809
1627
|
await recalculateThreadLastActivity(existing.threadId);
|
|
@@ -812,69 +1630,191 @@ export function createPostService(
|
|
|
812
1630
|
return hydratePost(result[0]);
|
|
813
1631
|
}
|
|
814
1632
|
|
|
815
|
-
// Complex case:
|
|
816
|
-
const
|
|
1633
|
+
// Complex case: cascade + update + collection sync atomically
|
|
1634
|
+
const existingCollectionIds = needsCollectionSync
|
|
1635
|
+
? await getCollectionIdsForPost(id)
|
|
1636
|
+
: [];
|
|
1637
|
+
let updateResult: (typeof posts.$inferSelect)[] | undefined;
|
|
1638
|
+
|
|
1639
|
+
if (usesBatchWrites) {
|
|
1640
|
+
const writeQueries = [];
|
|
1641
|
+
|
|
1642
|
+
if (needsCascade) {
|
|
1643
|
+
writeQueries.push(
|
|
1644
|
+
db
|
|
1645
|
+
.update(posts)
|
|
1646
|
+
.set({
|
|
1647
|
+
status: nextStatus,
|
|
1648
|
+
publishedAt:
|
|
1649
|
+
nextStatus === "published" ? nextPublishedAt : null,
|
|
1650
|
+
lastActivityAt:
|
|
1651
|
+
nextStatus === "published"
|
|
1652
|
+
? (nextPublishedAt ?? timestamp)
|
|
1653
|
+
: timestamp,
|
|
1654
|
+
updatedAt: timestamp,
|
|
1655
|
+
})
|
|
1656
|
+
.where(
|
|
1657
|
+
and(
|
|
1658
|
+
eq(posts.siteId, siteId),
|
|
1659
|
+
eq(posts.threadId, id),
|
|
1660
|
+
isNotNull(posts.replyToId),
|
|
1661
|
+
),
|
|
1662
|
+
),
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
if (needsReplyVisibilityCleanup) {
|
|
1667
|
+
writeQueries.push(
|
|
1668
|
+
db
|
|
1669
|
+
.update(posts)
|
|
1670
|
+
.set({ visibility: null, updatedAt: timestamp })
|
|
1671
|
+
.where(
|
|
1672
|
+
and(
|
|
1673
|
+
eq(posts.siteId, siteId),
|
|
1674
|
+
eq(posts.threadId, id),
|
|
1675
|
+
isNotNull(posts.replyToId),
|
|
1676
|
+
),
|
|
1677
|
+
),
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
817
1680
|
|
|
818
|
-
|
|
1681
|
+
const updateIdx = writeQueries.length;
|
|
819
1682
|
writeQueries.push(
|
|
820
1683
|
db
|
|
821
1684
|
.update(posts)
|
|
822
|
-
.set(
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
lastActivityAt:
|
|
826
|
-
nextStatus === "published"
|
|
827
|
-
? (nextPublishedAt ?? timestamp)
|
|
828
|
-
: timestamp,
|
|
829
|
-
updatedAt: timestamp,
|
|
830
|
-
})
|
|
831
|
-
.where(and(eq(posts.threadId, id), isNotNull(posts.replyToId))),
|
|
1685
|
+
.set(updates)
|
|
1686
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, id)))
|
|
1687
|
+
.returning(),
|
|
832
1688
|
);
|
|
833
|
-
}
|
|
834
1689
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1690
|
+
if (needsCollectionSync) {
|
|
1691
|
+
const existingIds = new Set(existingCollectionIds);
|
|
1692
|
+
const nextIds = new Set(nextCollectionIds);
|
|
1693
|
+
const removedIds = existingCollectionIds.filter(
|
|
1694
|
+
(cid) => !nextIds.has(cid),
|
|
1695
|
+
);
|
|
1696
|
+
const addedIds = nextCollectionIds.filter(
|
|
1697
|
+
(cid) => !existingIds.has(cid),
|
|
1698
|
+
);
|
|
843
1699
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
+
),
|
|
1710
|
+
),
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
849
1713
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
})),
|
|
864
|
-
),
|
|
865
|
-
);
|
|
1714
|
+
if (addedIds.length > 0) {
|
|
1715
|
+
const collectionTimestamp = now();
|
|
1716
|
+
writeQueries.push(
|
|
1717
|
+
db.insert(postCollections).values(
|
|
1718
|
+
addedIds.map((collectionId) => ({
|
|
1719
|
+
siteId,
|
|
1720
|
+
postId: id,
|
|
1721
|
+
collectionId,
|
|
1722
|
+
createdAt: collectionTimestamp,
|
|
1723
|
+
})),
|
|
1724
|
+
),
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
866
1727
|
}
|
|
1728
|
+
|
|
1729
|
+
const results = await db.batch(
|
|
1730
|
+
writeQueries as [
|
|
1731
|
+
(typeof writeQueries)[number],
|
|
1732
|
+
...(typeof writeQueries)[number][],
|
|
1733
|
+
],
|
|
1734
|
+
);
|
|
1735
|
+
updateResult = results[updateIdx] as
|
|
1736
|
+
| (typeof posts.$inferSelect)[]
|
|
1737
|
+
| undefined;
|
|
1738
|
+
} else {
|
|
1739
|
+
await db.transaction(async (tx) => {
|
|
1740
|
+
if (needsCascade) {
|
|
1741
|
+
await tx
|
|
1742
|
+
.update(posts)
|
|
1743
|
+
.set({
|
|
1744
|
+
status: nextStatus,
|
|
1745
|
+
publishedAt:
|
|
1746
|
+
nextStatus === "published" ? nextPublishedAt : null,
|
|
1747
|
+
lastActivityAt:
|
|
1748
|
+
nextStatus === "published"
|
|
1749
|
+
? (nextPublishedAt ?? timestamp)
|
|
1750
|
+
: timestamp,
|
|
1751
|
+
updatedAt: timestamp,
|
|
1752
|
+
})
|
|
1753
|
+
.where(
|
|
1754
|
+
and(
|
|
1755
|
+
eq(posts.siteId, siteId),
|
|
1756
|
+
eq(posts.threadId, id),
|
|
1757
|
+
isNotNull(posts.replyToId),
|
|
1758
|
+
),
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
if (needsReplyVisibilityCleanup) {
|
|
1763
|
+
await tx
|
|
1764
|
+
.update(posts)
|
|
1765
|
+
.set({ visibility: null, updatedAt: timestamp })
|
|
1766
|
+
.where(
|
|
1767
|
+
and(
|
|
1768
|
+
eq(posts.siteId, siteId),
|
|
1769
|
+
eq(posts.threadId, id),
|
|
1770
|
+
isNotNull(posts.replyToId),
|
|
1771
|
+
),
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
updateResult = await tx
|
|
1776
|
+
.update(posts)
|
|
1777
|
+
.set(updates)
|
|
1778
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, id)))
|
|
1779
|
+
.returning();
|
|
1780
|
+
|
|
1781
|
+
if (needsCollectionSync) {
|
|
1782
|
+
const existingIds = new Set(existingCollectionIds);
|
|
1783
|
+
const nextIds = new Set(nextCollectionIds);
|
|
1784
|
+
const removedIds = existingCollectionIds.filter(
|
|
1785
|
+
(cid) => !nextIds.has(cid),
|
|
1786
|
+
);
|
|
1787
|
+
const addedIds = nextCollectionIds.filter(
|
|
1788
|
+
(cid) => !existingIds.has(cid),
|
|
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
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (addedIds.length > 0) {
|
|
1804
|
+
const collectionTimestamp = now();
|
|
1805
|
+
await tx.insert(postCollections).values(
|
|
1806
|
+
addedIds.map((collectionId) => ({
|
|
1807
|
+
siteId,
|
|
1808
|
+
postId: id,
|
|
1809
|
+
collectionId,
|
|
1810
|
+
createdAt: collectionTimestamp,
|
|
1811
|
+
})),
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
867
1816
|
}
|
|
868
1817
|
|
|
869
|
-
const results = await db.batch(
|
|
870
|
-
writeQueries as [
|
|
871
|
-
(typeof writeQueries)[number],
|
|
872
|
-
...(typeof writeQueries)[number][],
|
|
873
|
-
],
|
|
874
|
-
);
|
|
875
|
-
const updateResult = results[updateIdx] as
|
|
876
|
-
| (typeof posts.$inferSelect)[]
|
|
877
|
-
| undefined;
|
|
878
1818
|
if (needsThreadActivityRecalc) {
|
|
879
1819
|
await recalculateThreadLastActivity(existing.threadId);
|
|
880
1820
|
return this.getById(id);
|
|
@@ -882,6 +1822,80 @@ export function createPostService(
|
|
|
882
1822
|
return hydratePost(updateResult?.[0]);
|
|
883
1823
|
},
|
|
884
1824
|
|
|
1825
|
+
async updateWithAttachments(id, data, attachments, deps, summaryConfig) {
|
|
1826
|
+
if (attachments === undefined) {
|
|
1827
|
+
return this.update(id, data, summaryConfig);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const existingPost = await this.getById(id);
|
|
1831
|
+
if (!existingPost) return null;
|
|
1832
|
+
|
|
1833
|
+
const existingCollectionIds = await getCollectionIdsForPost(id);
|
|
1834
|
+
const rollbackData = buildRollbackUpdate(
|
|
1835
|
+
existingPost,
|
|
1836
|
+
existingCollectionIds,
|
|
1837
|
+
);
|
|
1838
|
+
const existingAttachments = await deps.media.getByPostId(id);
|
|
1839
|
+
const previousMediaIds = existingAttachments.map(
|
|
1840
|
+
(attachment) => attachment.id,
|
|
1841
|
+
);
|
|
1842
|
+
const previousAltMap = new Map(
|
|
1843
|
+
existingAttachments.map((attachment) => [
|
|
1844
|
+
attachment.id,
|
|
1845
|
+
attachment.alt ?? "",
|
|
1846
|
+
]),
|
|
1847
|
+
);
|
|
1848
|
+
const { orderedMediaIds, createdTextMediaIds } =
|
|
1849
|
+
await createAttachmentMediaIds(attachments, deps);
|
|
1850
|
+
const post = await this.update(id, data, summaryConfig);
|
|
1851
|
+
|
|
1852
|
+
if (!post) {
|
|
1853
|
+
await cleanupCreatedTextAttachments(createdTextMediaIds, deps);
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
let replacedAttachments = false;
|
|
1858
|
+
|
|
1859
|
+
try {
|
|
1860
|
+
await deps.media.attachToPost(post.id, orderedMediaIds);
|
|
1861
|
+
replacedAttachments = true;
|
|
1862
|
+
await applyAttachmentAltUpdates(attachments, deps);
|
|
1863
|
+
|
|
1864
|
+
const nextAttachmentIds = new Set(orderedMediaIds);
|
|
1865
|
+
const removedTextAttachmentIds = existingAttachments
|
|
1866
|
+
.filter(
|
|
1867
|
+
(attachment) =>
|
|
1868
|
+
attachment.mimeType === "text/x-tiptap+json" &&
|
|
1869
|
+
!nextAttachmentIds.has(attachment.id),
|
|
1870
|
+
)
|
|
1871
|
+
.map((attachment) => attachment.id);
|
|
1872
|
+
await deps.media
|
|
1873
|
+
.deleteByIds(removedTextAttachmentIds, deps.storage)
|
|
1874
|
+
.catch(() => undefined);
|
|
1875
|
+
|
|
1876
|
+
return post;
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
if (replacedAttachments) {
|
|
1879
|
+
await deps.media
|
|
1880
|
+
.attachToPost(post.id, previousMediaIds)
|
|
1881
|
+
.catch(() => undefined);
|
|
1882
|
+
await Promise.all(
|
|
1883
|
+
existingAttachments.map((attachment) =>
|
|
1884
|
+
deps.media.updateAlt(
|
|
1885
|
+
attachment.id,
|
|
1886
|
+
previousAltMap.get(attachment.id) ?? "",
|
|
1887
|
+
),
|
|
1888
|
+
),
|
|
1889
|
+
).catch(() => undefined);
|
|
1890
|
+
}
|
|
1891
|
+
await this.update(id, rollbackData, summaryConfig).catch(
|
|
1892
|
+
() => undefined,
|
|
1893
|
+
);
|
|
1894
|
+
await cleanupCreatedTextAttachments(createdTextMediaIds, deps);
|
|
1895
|
+
throw error;
|
|
1896
|
+
}
|
|
1897
|
+
},
|
|
1898
|
+
|
|
885
1899
|
async delete(id, deps) {
|
|
886
1900
|
const existing = await this.getById(id);
|
|
887
1901
|
if (!existing) return false;
|
|
@@ -913,13 +1927,13 @@ export function createPostService(
|
|
|
913
1927
|
await db
|
|
914
1928
|
.update(posts)
|
|
915
1929
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
916
|
-
.where(eq(posts.threadId, id));
|
|
1930
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.threadId, id)));
|
|
917
1931
|
} else {
|
|
918
1932
|
// Soft-delete the single reply
|
|
919
1933
|
await db
|
|
920
1934
|
.update(posts)
|
|
921
1935
|
.set({ deletedAt: timestamp, updatedAt: timestamp })
|
|
922
|
-
.where(eq(posts.id, id));
|
|
1936
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, id)));
|
|
923
1937
|
await recalculateThreadLastActivity(existing.threadId);
|
|
924
1938
|
}
|
|
925
1939
|
|
|
@@ -930,36 +1944,82 @@ export function createPostService(
|
|
|
930
1944
|
const rows = await db
|
|
931
1945
|
.select()
|
|
932
1946
|
.from(posts)
|
|
933
|
-
.where(
|
|
1947
|
+
.where(
|
|
1948
|
+
and(
|
|
1949
|
+
eq(posts.siteId, siteId),
|
|
1950
|
+
eq(posts.threadId, rootId),
|
|
1951
|
+
isNull(posts.deletedAt),
|
|
1952
|
+
),
|
|
1953
|
+
)
|
|
934
1954
|
.orderBy(posts.createdAt);
|
|
935
1955
|
|
|
936
1956
|
return hydratePosts(rows);
|
|
937
1957
|
},
|
|
938
1958
|
|
|
939
1959
|
async updateThreadStatusAndVisibility(rootId, status, visibility) {
|
|
1960
|
+
const nextStatus = ensurePostStatus(status);
|
|
1961
|
+
const nextVisibility = ensurePostVisibility(visibility);
|
|
940
1962
|
const timestamp = now();
|
|
941
|
-
|
|
942
|
-
db
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1963
|
+
if (usesBatchWrites) {
|
|
1964
|
+
await db.batch([
|
|
1965
|
+
db
|
|
1966
|
+
.update(posts)
|
|
1967
|
+
.set({
|
|
1968
|
+
status: nextStatus,
|
|
1969
|
+
visibility: nextVisibility,
|
|
1970
|
+
publishedAt: nextStatus === "published" ? timestamp : null,
|
|
1971
|
+
lastActivityAt: timestamp,
|
|
1972
|
+
updatedAt: timestamp,
|
|
1973
|
+
})
|
|
1974
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, rootId))),
|
|
1975
|
+
db
|
|
1976
|
+
.update(posts)
|
|
1977
|
+
.set({
|
|
1978
|
+
status: nextStatus,
|
|
1979
|
+
visibility: null,
|
|
1980
|
+
publishedAt: nextStatus === "published" ? timestamp : null,
|
|
1981
|
+
lastActivityAt: timestamp,
|
|
1982
|
+
updatedAt: timestamp,
|
|
1983
|
+
})
|
|
1984
|
+
.where(
|
|
1985
|
+
and(
|
|
1986
|
+
eq(posts.siteId, siteId),
|
|
1987
|
+
eq(posts.threadId, rootId),
|
|
1988
|
+
isNotNull(posts.replyToId),
|
|
1989
|
+
),
|
|
1990
|
+
),
|
|
1991
|
+
]);
|
|
1992
|
+
} else {
|
|
1993
|
+
await db.transaction(async (tx) => {
|
|
1994
|
+
await tx
|
|
1995
|
+
.update(posts)
|
|
1996
|
+
.set({
|
|
1997
|
+
status: nextStatus,
|
|
1998
|
+
visibility: nextVisibility,
|
|
1999
|
+
publishedAt: nextStatus === "published" ? timestamp : null,
|
|
2000
|
+
lastActivityAt: timestamp,
|
|
2001
|
+
updatedAt: timestamp,
|
|
2002
|
+
})
|
|
2003
|
+
.where(and(eq(posts.siteId, siteId), eq(posts.id, rootId)));
|
|
2004
|
+
|
|
2005
|
+
await tx
|
|
2006
|
+
.update(posts)
|
|
2007
|
+
.set({
|
|
2008
|
+
status: nextStatus,
|
|
2009
|
+
visibility: null,
|
|
2010
|
+
publishedAt: nextStatus === "published" ? timestamp : null,
|
|
2011
|
+
lastActivityAt: timestamp,
|
|
2012
|
+
updatedAt: timestamp,
|
|
2013
|
+
})
|
|
2014
|
+
.where(
|
|
2015
|
+
and(
|
|
2016
|
+
eq(posts.siteId, siteId),
|
|
2017
|
+
eq(posts.threadId, rootId),
|
|
2018
|
+
isNotNull(posts.replyToId),
|
|
2019
|
+
),
|
|
2020
|
+
);
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
963
2023
|
await recalculateThreadLastActivity(rootId);
|
|
964
2024
|
},
|
|
965
2025
|
|
|
@@ -974,6 +2034,7 @@ export function createPostService(
|
|
|
974
2034
|
.from(posts)
|
|
975
2035
|
.where(
|
|
976
2036
|
and(
|
|
2037
|
+
eq(posts.siteId, siteId),
|
|
977
2038
|
inArray(posts.threadId, postIds),
|
|
978
2039
|
eq(posts.status, "published"),
|
|
979
2040
|
isNotNull(posts.replyToId),
|
|
@@ -992,29 +2053,57 @@ export function createPostService(
|
|
|
992
2053
|
async getThreadPreviews(rootIds, previewCount = 3) {
|
|
993
2054
|
if (rootIds.length === 0) return new Map();
|
|
994
2055
|
|
|
995
|
-
const
|
|
996
|
-
.select(
|
|
2056
|
+
const rankedReplies = db
|
|
2057
|
+
.select({
|
|
2058
|
+
id: posts.id,
|
|
2059
|
+
threadId: posts.threadId,
|
|
2060
|
+
createdAt: posts.createdAt,
|
|
2061
|
+
previewRank: sql<number>`ROW_NUMBER() OVER (
|
|
2062
|
+
PARTITION BY ${posts.threadId}
|
|
2063
|
+
ORDER BY ${posts.createdAt}, ${posts.id}
|
|
2064
|
+
)`.as("preview_rank"),
|
|
2065
|
+
})
|
|
997
2066
|
.from(posts)
|
|
998
2067
|
.where(
|
|
999
2068
|
and(
|
|
2069
|
+
eq(posts.siteId, siteId),
|
|
1000
2070
|
inArray(posts.threadId, rootIds),
|
|
1001
2071
|
eq(posts.status, "published"),
|
|
1002
2072
|
isNotNull(posts.replyToId),
|
|
1003
2073
|
isNull(posts.deletedAt),
|
|
1004
2074
|
),
|
|
1005
2075
|
)
|
|
1006
|
-
.
|
|
2076
|
+
.as("ranked_replies");
|
|
2077
|
+
|
|
2078
|
+
const rankedRows = await db
|
|
2079
|
+
.select({
|
|
2080
|
+
id: rankedReplies.id,
|
|
2081
|
+
threadId: rankedReplies.threadId,
|
|
2082
|
+
createdAt: rankedReplies.createdAt,
|
|
2083
|
+
})
|
|
2084
|
+
.from(rankedReplies)
|
|
2085
|
+
.where(lte(rankedReplies.previewRank, previewCount))
|
|
2086
|
+
.orderBy(
|
|
2087
|
+
rankedReplies.threadId,
|
|
2088
|
+
rankedReplies.createdAt,
|
|
2089
|
+
rankedReplies.id,
|
|
2090
|
+
);
|
|
1007
2091
|
|
|
2092
|
+
const hydratedPosts = await hydratePostsById(
|
|
2093
|
+
rankedRows.map((row) => row.id),
|
|
2094
|
+
);
|
|
1008
2095
|
const result = new Map<string, Post[]>();
|
|
1009
|
-
for (const
|
|
1010
|
-
const
|
|
2096
|
+
for (const row of rankedRows) {
|
|
2097
|
+
const post = hydratedPosts.get(row.id);
|
|
2098
|
+
if (!post) continue;
|
|
2099
|
+
|
|
2100
|
+
const list = result.get(row.threadId);
|
|
1011
2101
|
if (list) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
} else {
|
|
1016
|
-
result.set(post.threadId, [post]);
|
|
2102
|
+
list.push(post);
|
|
2103
|
+
continue;
|
|
1017
2104
|
}
|
|
2105
|
+
|
|
2106
|
+
result.set(row.threadId, [post]);
|
|
1018
2107
|
}
|
|
1019
2108
|
return result;
|
|
1020
2109
|
},
|
|
@@ -1022,46 +2111,347 @@ export function createPostService(
|
|
|
1022
2111
|
async getThreadTimelineContext(rootIds) {
|
|
1023
2112
|
if (rootIds.length === 0) return new Map();
|
|
1024
2113
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
2114
|
+
const rankedReplies = db
|
|
2115
|
+
.select({
|
|
2116
|
+
id: posts.id,
|
|
2117
|
+
threadId: posts.threadId,
|
|
2118
|
+
replyToId: posts.replyToId,
|
|
2119
|
+
replyRank: sql<number>`ROW_NUMBER() OVER (
|
|
2120
|
+
PARTITION BY ${posts.threadId}
|
|
2121
|
+
ORDER BY ${posts.createdAt} DESC, ${posts.id} DESC
|
|
2122
|
+
)`.as("reply_rank"),
|
|
2123
|
+
totalReplyCount: sql<number>`COUNT(*) OVER (
|
|
2124
|
+
PARTITION BY ${posts.threadId}
|
|
2125
|
+
)`.as("total_reply_count"),
|
|
2126
|
+
})
|
|
1028
2127
|
.from(posts)
|
|
1029
2128
|
.where(
|
|
1030
2129
|
and(
|
|
2130
|
+
eq(posts.siteId, siteId),
|
|
1031
2131
|
inArray(posts.threadId, rootIds),
|
|
1032
2132
|
eq(posts.status, "published"),
|
|
1033
2133
|
isNotNull(posts.replyToId),
|
|
1034
2134
|
isNull(posts.deletedAt),
|
|
1035
2135
|
),
|
|
1036
2136
|
)
|
|
1037
|
-
.
|
|
2137
|
+
.as("ranked_replies");
|
|
2138
|
+
|
|
2139
|
+
const latestReplyRows = await db
|
|
2140
|
+
.select({
|
|
2141
|
+
threadId: rankedReplies.threadId,
|
|
2142
|
+
latestReplyId: rankedReplies.id,
|
|
2143
|
+
latestReplyToId: rankedReplies.replyToId,
|
|
2144
|
+
totalReplyCount: rankedReplies.totalReplyCount,
|
|
2145
|
+
})
|
|
2146
|
+
.from(rankedReplies)
|
|
2147
|
+
.where(eq(rankedReplies.replyRank, 1));
|
|
2148
|
+
|
|
2149
|
+
const relatedPostIds = latestReplyRows.flatMap((row) => {
|
|
2150
|
+
const ids = [row.latestReplyId];
|
|
2151
|
+
|
|
2152
|
+
if (row.latestReplyToId && row.latestReplyToId !== row.threadId) {
|
|
2153
|
+
ids.push(row.latestReplyToId);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
return ids;
|
|
2157
|
+
});
|
|
2158
|
+
const hydratedPosts = await hydratePostsById(relatedPostIds);
|
|
2159
|
+
|
|
2160
|
+
const result = new Map<string, ThreadTimelineContext>();
|
|
2161
|
+
for (const row of latestReplyRows) {
|
|
2162
|
+
const latestReply = hydratedPosts.get(row.latestReplyId);
|
|
2163
|
+
if (!latestReply) continue;
|
|
2164
|
+
|
|
2165
|
+
const parentReply =
|
|
2166
|
+
row.latestReplyToId && row.latestReplyToId !== row.threadId
|
|
2167
|
+
? (hydratedPosts.get(row.latestReplyToId) ?? null)
|
|
2168
|
+
: null;
|
|
2169
|
+
|
|
2170
|
+
result.set(row.threadId, {
|
|
2171
|
+
latestReply,
|
|
2172
|
+
parentReply,
|
|
2173
|
+
totalReplyCount: row.totalReplyCount,
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
return result;
|
|
2178
|
+
},
|
|
2179
|
+
|
|
2180
|
+
async countFeaturedThreadRoots(options = {}) {
|
|
2181
|
+
const conditions = [
|
|
2182
|
+
...buildThreadRootPageConditions(options),
|
|
2183
|
+
isNotNull(posts.featuredAt),
|
|
2184
|
+
];
|
|
2185
|
+
|
|
2186
|
+
const rows = await db
|
|
2187
|
+
.select({
|
|
2188
|
+
count: sql<number>`count(distinct ${posts.threadId})`.as("count"),
|
|
2189
|
+
})
|
|
2190
|
+
.from(posts)
|
|
2191
|
+
.where(and(...conditions));
|
|
2192
|
+
|
|
2193
|
+
return rows[0]?.count ?? 0;
|
|
2194
|
+
},
|
|
2195
|
+
|
|
2196
|
+
async listFeaturedThreadRootIds(options = {}) {
|
|
2197
|
+
const conditions = [
|
|
2198
|
+
...buildThreadRootPageConditions(options),
|
|
2199
|
+
isNotNull(posts.featuredAt),
|
|
2200
|
+
];
|
|
2201
|
+
const latestFeaturedAt = sql<number>`MAX(${posts.featuredAt})`.as(
|
|
2202
|
+
"latest_featured_at",
|
|
2203
|
+
);
|
|
2204
|
+
|
|
2205
|
+
let query = db
|
|
2206
|
+
.select({
|
|
2207
|
+
threadId: posts.threadId,
|
|
2208
|
+
latestFeaturedAt,
|
|
2209
|
+
})
|
|
2210
|
+
.from(posts)
|
|
2211
|
+
.where(and(...conditions))
|
|
2212
|
+
.groupBy(posts.threadId)
|
|
2213
|
+
.orderBy(desc(latestFeaturedAt), desc(posts.threadId));
|
|
2214
|
+
|
|
2215
|
+
if (options.limit !== undefined) {
|
|
2216
|
+
query = query.limit(options.limit) as typeof query;
|
|
2217
|
+
}
|
|
2218
|
+
if (options.offset !== undefined) {
|
|
2219
|
+
query = query.offset(options.offset) as typeof query;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
const rows = await query;
|
|
2223
|
+
return rows.map((row) => row.threadId);
|
|
2224
|
+
},
|
|
2225
|
+
|
|
2226
|
+
async countCollectionThreadRoots(collectionId, options = {}) {
|
|
2227
|
+
return this.countCollectionThreadRootsForCollections(
|
|
2228
|
+
[collectionId],
|
|
2229
|
+
options,
|
|
2230
|
+
);
|
|
2231
|
+
},
|
|
2232
|
+
|
|
2233
|
+
async countCollectionThreadRootsForCollections(
|
|
2234
|
+
collectionIds,
|
|
2235
|
+
options = {},
|
|
2236
|
+
) {
|
|
2237
|
+
const conditions = [
|
|
2238
|
+
...buildThreadRootPageConditions(options),
|
|
2239
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2240
|
+
];
|
|
2241
|
+
|
|
2242
|
+
const rows = await db
|
|
2243
|
+
.select({
|
|
2244
|
+
count: sql<number>`count(distinct ${posts.threadId})`.as("count"),
|
|
2245
|
+
})
|
|
2246
|
+
.from(posts)
|
|
2247
|
+
.innerJoin(
|
|
2248
|
+
postCollections,
|
|
2249
|
+
and(
|
|
2250
|
+
eq(postCollections.siteId, siteId),
|
|
2251
|
+
eq(postCollections.postId, posts.id),
|
|
2252
|
+
),
|
|
2253
|
+
)
|
|
2254
|
+
.where(and(...conditions));
|
|
2255
|
+
|
|
2256
|
+
return rows[0]?.count ?? 0;
|
|
2257
|
+
},
|
|
2258
|
+
|
|
2259
|
+
async listCollectionThreadRootIds(collectionId, options = {}) {
|
|
2260
|
+
return this.listCollectionThreadRootIdsForCollections(
|
|
2261
|
+
[collectionId],
|
|
2262
|
+
options,
|
|
2263
|
+
);
|
|
2264
|
+
},
|
|
2265
|
+
|
|
2266
|
+
async listCollectionThreadRootIdsForCollections(
|
|
2267
|
+
collectionIds,
|
|
2268
|
+
options = {},
|
|
2269
|
+
) {
|
|
2270
|
+
const conditions = [
|
|
2271
|
+
...buildThreadRootPageConditions(options),
|
|
2272
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2273
|
+
];
|
|
2274
|
+
const sortOrder = options.sortOrder ?? "newest";
|
|
2275
|
+
const collectedAt =
|
|
2276
|
+
sortOrder === "oldest"
|
|
2277
|
+
? sql<number>`MIN(${postCollections.createdAt})`.as("collected_at")
|
|
2278
|
+
: sql<number>`MAX(${postCollections.createdAt})`.as("collected_at");
|
|
2279
|
+
const ratingPresence = sql<number>`MAX(
|
|
2280
|
+
CASE
|
|
2281
|
+
WHEN ${posts.rating} IS NULL THEN 0
|
|
2282
|
+
ELSE 1
|
|
2283
|
+
END
|
|
2284
|
+
)`.as("rating_presence");
|
|
2285
|
+
const ratingValue = sql<number | null>`MAX(${posts.rating})`.as(
|
|
2286
|
+
"rating_value",
|
|
2287
|
+
);
|
|
2288
|
+
|
|
2289
|
+
const baseQuery = db
|
|
2290
|
+
.select({
|
|
2291
|
+
threadId: posts.threadId,
|
|
2292
|
+
collectedAt,
|
|
2293
|
+
ratingPresence,
|
|
2294
|
+
ratingValue,
|
|
2295
|
+
})
|
|
2296
|
+
.from(posts)
|
|
2297
|
+
.innerJoin(
|
|
2298
|
+
postCollections,
|
|
2299
|
+
and(
|
|
2300
|
+
eq(postCollections.siteId, siteId),
|
|
2301
|
+
eq(postCollections.postId, posts.id),
|
|
2302
|
+
),
|
|
2303
|
+
)
|
|
2304
|
+
.where(and(...conditions))
|
|
2305
|
+
.groupBy(posts.threadId);
|
|
2306
|
+
|
|
2307
|
+
let query =
|
|
2308
|
+
sortOrder === "oldest"
|
|
2309
|
+
? baseQuery.orderBy(asc(collectedAt), asc(posts.threadId))
|
|
2310
|
+
: sortOrder === "rating_desc"
|
|
2311
|
+
? baseQuery.orderBy(
|
|
2312
|
+
desc(ratingPresence),
|
|
2313
|
+
desc(ratingValue),
|
|
2314
|
+
desc(collectedAt),
|
|
2315
|
+
desc(posts.threadId),
|
|
2316
|
+
)
|
|
2317
|
+
: baseQuery.orderBy(desc(collectedAt), desc(posts.threadId));
|
|
2318
|
+
|
|
2319
|
+
if (options.limit !== undefined) {
|
|
2320
|
+
query = query.limit(options.limit) as typeof query;
|
|
2321
|
+
}
|
|
2322
|
+
if (options.offset !== undefined) {
|
|
2323
|
+
query = query.offset(options.offset) as typeof query;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const rows = await query;
|
|
2327
|
+
return rows.map((row) => row.threadId);
|
|
2328
|
+
},
|
|
2329
|
+
|
|
2330
|
+
async listCollectionFeedEntries(collectionId, options = {}) {
|
|
2331
|
+
return this.listCollectionFeedEntriesForCollections(
|
|
2332
|
+
[collectionId],
|
|
2333
|
+
options,
|
|
2334
|
+
);
|
|
2335
|
+
},
|
|
2336
|
+
|
|
2337
|
+
async listCollectionFeedEntriesForCollections(collectionIds, options = {}) {
|
|
2338
|
+
const conditions = [
|
|
2339
|
+
...buildThreadRootPageConditions(options),
|
|
2340
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2341
|
+
];
|
|
2342
|
+
const collectedAt = sql<number>`MAX(${postCollections.createdAt})`.as(
|
|
2343
|
+
"collected_at",
|
|
2344
|
+
);
|
|
2345
|
+
|
|
2346
|
+
let query = db
|
|
2347
|
+
.select({
|
|
2348
|
+
threadId: posts.threadId,
|
|
2349
|
+
collectedAt,
|
|
2350
|
+
})
|
|
2351
|
+
.from(posts)
|
|
2352
|
+
.innerJoin(
|
|
2353
|
+
postCollections,
|
|
2354
|
+
and(
|
|
2355
|
+
eq(postCollections.siteId, siteId),
|
|
2356
|
+
eq(postCollections.postId, posts.id),
|
|
2357
|
+
),
|
|
2358
|
+
)
|
|
2359
|
+
.where(and(...conditions))
|
|
2360
|
+
.groupBy(posts.threadId)
|
|
2361
|
+
.orderBy(desc(collectedAt), desc(posts.threadId));
|
|
2362
|
+
|
|
2363
|
+
if (options.limit !== undefined) {
|
|
2364
|
+
query = query.limit(options.limit) as typeof query;
|
|
2365
|
+
}
|
|
2366
|
+
if (options.offset !== undefined) {
|
|
2367
|
+
query = query.offset(options.offset) as typeof query;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
const rows = await query;
|
|
2371
|
+
const postsById = await hydratePostsById(rows.map((row) => row.threadId));
|
|
2372
|
+
|
|
2373
|
+
return rows.flatMap((row) => {
|
|
2374
|
+
const post = postsById.get(row.threadId);
|
|
2375
|
+
return post ? [{ post, collectedAt: row.collectedAt }] : [];
|
|
2376
|
+
});
|
|
2377
|
+
},
|
|
2378
|
+
|
|
2379
|
+
async getPublishedThreads(rootIds) {
|
|
2380
|
+
const result = new Map<string, Post[]>();
|
|
2381
|
+
if (rootIds.length === 0) return result;
|
|
2382
|
+
|
|
2383
|
+
const unique = [...new Set(rootIds)];
|
|
2384
|
+
const rows = await db
|
|
2385
|
+
.select()
|
|
2386
|
+
.from(posts)
|
|
2387
|
+
.where(
|
|
2388
|
+
and(
|
|
2389
|
+
eq(posts.siteId, siteId),
|
|
2390
|
+
inArray(posts.threadId, unique),
|
|
2391
|
+
eq(posts.status, "published"),
|
|
2392
|
+
isNull(posts.deletedAt),
|
|
2393
|
+
),
|
|
2394
|
+
)
|
|
2395
|
+
.orderBy(posts.threadId, posts.createdAt, posts.id);
|
|
1038
2396
|
|
|
1039
|
-
// Group by threadId, extract latest reply + its parent + count
|
|
1040
|
-
const grouped = new Map<string, Post[]>();
|
|
1041
2397
|
for (const post of await hydratePosts(rows)) {
|
|
1042
|
-
const
|
|
1043
|
-
if (
|
|
1044
|
-
|
|
2398
|
+
const thread = result.get(post.threadId);
|
|
2399
|
+
if (thread) {
|
|
2400
|
+
thread.push(post);
|
|
1045
2401
|
} else {
|
|
1046
|
-
|
|
2402
|
+
result.set(post.threadId, [post]);
|
|
1047
2403
|
}
|
|
1048
2404
|
}
|
|
1049
2405
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
2406
|
+
return result;
|
|
2407
|
+
},
|
|
2408
|
+
|
|
2409
|
+
async getCollectionPostIdsByThread(collectionId, threadIds) {
|
|
2410
|
+
return this.getCollectionPostIdsByThreadForCollections(
|
|
2411
|
+
[collectionId],
|
|
2412
|
+
threadIds,
|
|
2413
|
+
);
|
|
2414
|
+
},
|
|
2415
|
+
|
|
2416
|
+
async getCollectionPostIdsByThreadForCollections(collectionIds, threadIds) {
|
|
2417
|
+
const result = new Map<string, string[]>();
|
|
2418
|
+
if (threadIds.length === 0) return result;
|
|
2419
|
+
|
|
2420
|
+
const unique = [...new Set(threadIds)];
|
|
2421
|
+
const rows = await batchQueryRows(unique, (chunk) =>
|
|
2422
|
+
db
|
|
2423
|
+
.select({
|
|
2424
|
+
threadId: posts.threadId,
|
|
2425
|
+
postId: posts.id,
|
|
2426
|
+
})
|
|
2427
|
+
.from(posts)
|
|
2428
|
+
.innerJoin(
|
|
2429
|
+
postCollections,
|
|
2430
|
+
and(
|
|
2431
|
+
eq(postCollections.siteId, siteId),
|
|
2432
|
+
eq(postCollections.postId, posts.id),
|
|
2433
|
+
),
|
|
2434
|
+
)
|
|
2435
|
+
.where(
|
|
2436
|
+
and(
|
|
2437
|
+
eq(posts.siteId, siteId),
|
|
2438
|
+
buildCollectionMembershipCondition(collectionIds),
|
|
2439
|
+
inArray(posts.threadId, chunk),
|
|
2440
|
+
eq(posts.status, "published"),
|
|
2441
|
+
isNull(posts.deletedAt),
|
|
2442
|
+
),
|
|
2443
|
+
)
|
|
2444
|
+
.groupBy(posts.threadId, posts.id)
|
|
2445
|
+
.orderBy(posts.threadId, posts.createdAt, posts.id),
|
|
2446
|
+
);
|
|
1063
2447
|
|
|
1064
|
-
|
|
2448
|
+
for (const row of rows) {
|
|
2449
|
+
const list = result.get(row.threadId);
|
|
2450
|
+
if (list) {
|
|
2451
|
+
list.push(row.postId);
|
|
2452
|
+
} else {
|
|
2453
|
+
result.set(row.threadId, [row.postId]);
|
|
2454
|
+
}
|
|
1065
2455
|
}
|
|
1066
2456
|
|
|
1067
2457
|
return result;
|
|
@@ -1075,20 +2465,23 @@ export function createPostService(
|
|
|
1075
2465
|
const rows = await db
|
|
1076
2466
|
.select({
|
|
1077
2467
|
threadId: posts.threadId,
|
|
1078
|
-
id:
|
|
1079
|
-
SELECT p2.id FROM post AS p2
|
|
1080
|
-
WHERE p2.thread_id = ${posts.threadId}
|
|
1081
|
-
AND p2.deleted_at IS NULL
|
|
1082
|
-
AND p2.status = 'published'
|
|
1083
|
-
ORDER BY p2.created_at DESC, p2.id DESC
|
|
1084
|
-
LIMIT 1
|
|
1085
|
-
)`.as("last_id"),
|
|
2468
|
+
id: posts.id,
|
|
1086
2469
|
})
|
|
1087
2470
|
.from(posts)
|
|
1088
|
-
.where(
|
|
2471
|
+
.where(
|
|
2472
|
+
and(
|
|
2473
|
+
eq(posts.siteId, siteId),
|
|
2474
|
+
inArray(posts.threadId, unique),
|
|
2475
|
+
eq(posts.status, "published"),
|
|
2476
|
+
isNull(posts.deletedAt),
|
|
2477
|
+
),
|
|
2478
|
+
)
|
|
2479
|
+
.orderBy(posts.threadId, desc(posts.createdAt), desc(posts.id));
|
|
1089
2480
|
|
|
1090
2481
|
for (const row of rows) {
|
|
1091
|
-
if (
|
|
2482
|
+
if (!result.has(row.threadId)) {
|
|
2483
|
+
result.set(row.threadId, row.id);
|
|
2484
|
+
}
|
|
1092
2485
|
}
|
|
1093
2486
|
return result;
|
|
1094
2487
|
},
|
|
@@ -1098,17 +2491,16 @@ export function createPostService(
|
|
|
1098
2491
|
...buildFilterConditions(filters),
|
|
1099
2492
|
isNotNull(posts.publishedAt),
|
|
1100
2493
|
];
|
|
2494
|
+
const publishedYearExpr = buildPublishedYearExpr();
|
|
1101
2495
|
|
|
1102
2496
|
const rows = await db
|
|
1103
2497
|
.select({
|
|
1104
|
-
year:
|
|
1105
|
-
"year",
|
|
1106
|
-
),
|
|
2498
|
+
year: publishedYearExpr.as("year"),
|
|
1107
2499
|
})
|
|
1108
2500
|
.from(posts)
|
|
1109
2501
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
1110
|
-
.groupBy(
|
|
1111
|
-
.orderBy(desc(
|
|
2502
|
+
.groupBy(publishedYearExpr)
|
|
2503
|
+
.orderBy(desc(publishedYearExpr));
|
|
1112
2504
|
|
|
1113
2505
|
return rows.map((r) => parseInt(r.year, 10));
|
|
1114
2506
|
},
|