@jant/core 0.3.38 → 0.3.39
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 +5 -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-BfoG98VD.js +30410 -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 +3251 -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 +272 -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 +250 -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 +338 -509
- package/src/client/components/jant-collection-sidebar.ts +461 -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 +1778 -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 +1819 -272
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +1823 -276
- 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-sort.test.ts +45 -0
- package/src/lib/__tests__/constants.test.ts +1 -0
- 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 +268 -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 +46 -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 +410 -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-sort.ts +61 -0
- package/src/lib/confirm.ts +37 -0
- package/src/lib/constants.ts +3 -6
- 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 +264 -73
- package/src/lib/search-snippet.ts +110 -8
- package/src/lib/site-resolution.ts +28 -0
- package/src/lib/slug-format.ts +84 -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 +386 -62
- 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 +143 -59
- 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/cloudflare.ts +102 -0
- package/src/runtime/index.ts +14 -0
- package/src/runtime/node.ts +187 -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 +154 -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 +111 -2
- package/src/services/__tests__/post.test.ts +463 -38
- 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 +501 -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 +1529 -246
- 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 +7586 -3401
- 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 +46 -9
- 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 +410 -18
- 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 +117 -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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
3
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
4
|
+
import type { Editor } from "@tiptap/core";
|
|
5
|
+
|
|
4
6
|
import type {
|
|
5
7
|
ComposeLabels,
|
|
6
8
|
ComposeCollection,
|
|
@@ -21,6 +23,46 @@ function requireElement<T extends globalThis.Element>(
|
|
|
21
23
|
return element;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function requireEditor(el: JantComposeEditor): Editor {
|
|
27
|
+
const editor = (el as unknown as { _editor?: Editor | null })._editor;
|
|
28
|
+
if (!editor) {
|
|
29
|
+
throw new Error("expected compose editor instance");
|
|
30
|
+
}
|
|
31
|
+
return editor;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function flushUpdates(el?: JantComposeDialog) {
|
|
35
|
+
await Promise.resolve();
|
|
36
|
+
await Promise.resolve();
|
|
37
|
+
if (el) {
|
|
38
|
+
await el.updateComplete;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mockSlugApi(
|
|
43
|
+
handler: (url: URL) => { status?: number; body: unknown },
|
|
44
|
+
): ReturnType<typeof vi.spyOn> {
|
|
45
|
+
return vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
|
|
46
|
+
const raw =
|
|
47
|
+
typeof input === "string"
|
|
48
|
+
? input
|
|
49
|
+
: input instanceof URL
|
|
50
|
+
? input.toString()
|
|
51
|
+
: input.url;
|
|
52
|
+
const url = new URL(raw, "http://localhost");
|
|
53
|
+
|
|
54
|
+
if (url.pathname === "/api/posts/slug") {
|
|
55
|
+
const { status = 200, body } = handler(url);
|
|
56
|
+
return new Response(JSON.stringify(body), {
|
|
57
|
+
status,
|
|
58
|
+
headers: { "Content-Type": "application/json" },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(`Unexpected fetch: ${url.pathname}${url.search}`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
24
66
|
const labels: ComposeLabels = {
|
|
25
67
|
cancel: "Cancel",
|
|
26
68
|
note: "Note",
|
|
@@ -32,6 +74,9 @@ const labels: ComposeLabels = {
|
|
|
32
74
|
titlePlaceholder: "Title",
|
|
33
75
|
bodyPlaceholder: "What's on your mind...",
|
|
34
76
|
urlPlaceholder: "Paste a URL...",
|
|
77
|
+
urlInvalid: "Enter a valid URL starting with http://, https://, or mailto:.",
|
|
78
|
+
linkUrlRequired: "Add a URL before posting this link.",
|
|
79
|
+
linkTitleRequired: "Add a title before posting this link.",
|
|
35
80
|
linkTitlePlaceholder: "Give it a title...",
|
|
36
81
|
thoughtsPlaceholder: "Your thoughts (optional)",
|
|
37
82
|
quotePlaceholder: "Type the quote...",
|
|
@@ -45,9 +90,10 @@ const labels: ComposeLabels = {
|
|
|
45
90
|
rate: "Rate",
|
|
46
91
|
emoji: "Emoji",
|
|
47
92
|
title: "Title",
|
|
93
|
+
fullscreen: "Fullscreen",
|
|
48
94
|
collection: "Collection",
|
|
49
95
|
searchCollections: "Search...",
|
|
50
|
-
noCollections: "No collections
|
|
96
|
+
noCollections: "No collections match that search. Try a different name.",
|
|
51
97
|
emptyCollections: "Create a collection to get started.",
|
|
52
98
|
post: "Post",
|
|
53
99
|
addAlt: "+ ALT",
|
|
@@ -55,6 +101,7 @@ const labels: ComposeLabels = {
|
|
|
55
101
|
altPlaceholder: "Describe this...",
|
|
56
102
|
altHint: "Alt text improves accessibility",
|
|
57
103
|
addMore: "Add",
|
|
104
|
+
removeAttachment: "Remove attachment",
|
|
58
105
|
uploading: "Uploading...",
|
|
59
106
|
published: "Published!",
|
|
60
107
|
view: "View",
|
|
@@ -66,10 +113,16 @@ const labels: ComposeLabels = {
|
|
|
66
113
|
confirmCloseSave: "Save",
|
|
67
114
|
confirmCloseCancel: "Cancel",
|
|
68
115
|
confirmCloseDiscard: "Don't save",
|
|
116
|
+
confirmAttachedTitle: "Save text attachment?",
|
|
117
|
+
confirmAttachedSubtitle:
|
|
118
|
+
"Save these changes to the text attachment, discard them, or keep editing.",
|
|
119
|
+
confirmAttachedSave: "Save",
|
|
120
|
+
confirmAttachedDiscard: "Don't save",
|
|
69
121
|
confirmEditTitle: "You have unsaved changes",
|
|
70
122
|
confirmEditSubtitle: "Do you want to publish your changes or discard them?",
|
|
71
123
|
confirmEditPublish: "Publish",
|
|
72
124
|
confirmEditDiscard: "Discard",
|
|
125
|
+
discardChangesConfirm: "Discard changes?",
|
|
73
126
|
drafts: "Drafts",
|
|
74
127
|
draftsEmpty: "No drafts yet. Save a draft to find it here.",
|
|
75
128
|
deleteDraft: "Delete Draft",
|
|
@@ -80,36 +133,58 @@ const labels: ComposeLabels = {
|
|
|
80
133
|
collectionCountLabel: "%name% + %count% more",
|
|
81
134
|
draftRestored: "Draft restored.",
|
|
82
135
|
reply: "Reply",
|
|
83
|
-
|
|
84
|
-
publishUnlisted: "Post Unlisted",
|
|
136
|
+
publishHideFromLatest: "Hide from Latest",
|
|
85
137
|
publishPrivate: "Post as Private",
|
|
138
|
+
publishSettings: "Publish settings",
|
|
139
|
+
publishVisibilityLabel: "Visibility",
|
|
140
|
+
publishVisibilityPublic: "Public",
|
|
141
|
+
publishVisibilityPublicHint: "Appears in Latest.",
|
|
142
|
+
publishVisibilityHiddenFromLatest: "Hidden from Latest",
|
|
143
|
+
publishVisibilityHiddenFromLatestHint:
|
|
144
|
+
"Doesn't appear in Latest. Still appears in collections you add it to.",
|
|
145
|
+
publishVisibilityPrivate: "Private",
|
|
146
|
+
publishVisibilityPrivateHint: "Only visible when signed in.",
|
|
147
|
+
publishSlugLabel: "Custom link",
|
|
148
|
+
publishSlugPlaceholder: "your-post-link",
|
|
149
|
+
publishSlugHint: "Leave blank to generate one automatically.",
|
|
150
|
+
publishSlugAuto: "Generate automatically",
|
|
151
|
+
publishSlugReset: "Reset link",
|
|
152
|
+
publishSlugSuggested: "Suggested link",
|
|
153
|
+
publishSlugGenerating: "Generating a link...",
|
|
154
|
+
publishSlugChecking: "Checking link...",
|
|
155
|
+
publishSlugTaken: "This link is already in use. Choose something else.",
|
|
156
|
+
publishSlugInvalid: "Use lowercase letters, numbers, and hyphens only.",
|
|
157
|
+
publishSlugReserved: "This link is reserved. Choose something else.",
|
|
158
|
+
postHiddenFromLatest: "Post hidden",
|
|
159
|
+
postPrivately: "Post privately",
|
|
86
160
|
showMore: "Show more",
|
|
87
161
|
showLess: "Show less",
|
|
88
162
|
collectionFormLabels: {
|
|
89
163
|
titleLabel: "Title",
|
|
90
164
|
titlePlaceholder: "My Collection",
|
|
91
|
-
slugLabel: "
|
|
92
|
-
slugHelp: "
|
|
165
|
+
slugLabel: "Collection link",
|
|
166
|
+
slugHelp: "This is the last part of the collection link.",
|
|
167
|
+
slugInvalidHelp: "Use lowercase letters, numbers, and hyphens only.",
|
|
168
|
+
slugReservedHelp: "This link is reserved. Choose something else.",
|
|
169
|
+
editSlugLabel: "Edit link",
|
|
170
|
+
resetSlugLabel: "Reset link",
|
|
171
|
+
quickHint: "More options are available after you create it.",
|
|
172
|
+
quickSubmitLabel: "Done",
|
|
173
|
+
createdLabel: "Collection created.",
|
|
93
174
|
descriptionLabel: "Description (optional)",
|
|
94
175
|
descriptionPlaceholder: "What's this collection about?",
|
|
95
|
-
removeIcon: "Remove",
|
|
96
|
-
iconsTab: "Icons",
|
|
97
|
-
emojisTab: "Emojis",
|
|
98
|
-
searchIconsPlaceholder: "Search icons...",
|
|
99
|
-
searchEmojisPlaceholder: "Search emojis...",
|
|
100
176
|
sortOrderLabel: "Sort Order",
|
|
101
177
|
sortNewest: "Newest first",
|
|
102
178
|
sortOldest: "Oldest first",
|
|
103
179
|
sortRatingDesc: "Highest rated",
|
|
104
|
-
sortRatingAsc: "Lowest rated",
|
|
105
180
|
submitLabel: "Save",
|
|
106
181
|
cancelLabel: "Cancel",
|
|
107
182
|
},
|
|
108
183
|
};
|
|
109
184
|
|
|
110
185
|
const collections: ComposeCollection[] = [
|
|
111
|
-
{ id: "col-1", title: "Books"
|
|
112
|
-
{ id: "col-2", title: "Movies"
|
|
186
|
+
{ id: "col-1", title: "Books" },
|
|
187
|
+
{ id: "col-2", title: "Movies" },
|
|
113
188
|
];
|
|
114
189
|
|
|
115
190
|
async function createElement(
|
|
@@ -128,7 +203,14 @@ async function createElement(
|
|
|
128
203
|
|
|
129
204
|
describe("JantComposeDialog", () => {
|
|
130
205
|
beforeEach(() => {
|
|
206
|
+
vi.restoreAllMocks();
|
|
131
207
|
document.body.innerHTML = "";
|
|
208
|
+
globalThis.localStorage.clear();
|
|
209
|
+
(
|
|
210
|
+
customElements.get("jant-compose-dialog") as typeof HTMLElement & {
|
|
211
|
+
_lastNewPostVisibility: string;
|
|
212
|
+
}
|
|
213
|
+
)._lastNewPostVisibility = "public";
|
|
132
214
|
});
|
|
133
215
|
|
|
134
216
|
it("renders with collections and labels", async () => {
|
|
@@ -150,6 +232,97 @@ describe("JantComposeDialog", () => {
|
|
|
150
232
|
"expected post button",
|
|
151
233
|
);
|
|
152
234
|
expect(postBtn.textContent?.trim()).toBe("Post");
|
|
235
|
+
expect(postBtn.disabled).toBe(true);
|
|
236
|
+
expect(
|
|
237
|
+
requireElement(
|
|
238
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
239
|
+
"expected publish settings toggle",
|
|
240
|
+
).disabled,
|
|
241
|
+
).toBe(false);
|
|
242
|
+
expect(
|
|
243
|
+
el.querySelector<HTMLButtonElement>(
|
|
244
|
+
'.compose-tool-btn-view[aria-label="Fullscreen"]',
|
|
245
|
+
),
|
|
246
|
+
).not.toBeNull();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("focuses the main editor body after fullscreen closes", async () => {
|
|
250
|
+
const el = await createElement();
|
|
251
|
+
const editor = requireElement(
|
|
252
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
253
|
+
"expected compose editor",
|
|
254
|
+
);
|
|
255
|
+
const focusSpy = vi.spyOn(editor, "focusInput");
|
|
256
|
+
|
|
257
|
+
document.dispatchEvent(
|
|
258
|
+
new CustomEvent("jant:fullscreen-close", {
|
|
259
|
+
detail: {
|
|
260
|
+
json: null,
|
|
261
|
+
title: "",
|
|
262
|
+
showTitle: false,
|
|
263
|
+
replyExpanded: false,
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
await flushUpdates(el);
|
|
269
|
+
await flushUpdates(el);
|
|
270
|
+
|
|
271
|
+
expect(focusSpy).toHaveBeenCalledWith("end");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("opens publish settings even when publish is disabled", async () => {
|
|
275
|
+
const el = await createElement();
|
|
276
|
+
|
|
277
|
+
requireElement(
|
|
278
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
279
|
+
"expected publish settings toggle",
|
|
280
|
+
).click();
|
|
281
|
+
await el.updateComplete;
|
|
282
|
+
|
|
283
|
+
expect(el.querySelector(".compose-publish-panel")).not.toBeNull();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("updates the draft button label to match its current action", async () => {
|
|
287
|
+
const el = await createElement();
|
|
288
|
+
|
|
289
|
+
const draftButton = requireElement(
|
|
290
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-draft-btn"),
|
|
291
|
+
"expected draft button",
|
|
292
|
+
);
|
|
293
|
+
expect(draftButton.title).toBe("Drafts");
|
|
294
|
+
expect(draftButton.getAttribute("aria-label")).toBe("Drafts");
|
|
295
|
+
|
|
296
|
+
const editor = requireElement(
|
|
297
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
298
|
+
"expected compose editor",
|
|
299
|
+
);
|
|
300
|
+
editor._bodyJson = {
|
|
301
|
+
type: "doc",
|
|
302
|
+
content: [
|
|
303
|
+
{ type: "paragraph", content: [{ type: "text", text: "Hello world" }] },
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
await editor.updateComplete;
|
|
307
|
+
await el.updateComplete;
|
|
308
|
+
|
|
309
|
+
expect(draftButton.title).toBe("Save as draft");
|
|
310
|
+
expect(draftButton.getAttribute("aria-label")).toBe("Save as draft");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("shows visibility hints in the publish settings panel", async () => {
|
|
314
|
+
const el = await createElement();
|
|
315
|
+
|
|
316
|
+
requireElement(
|
|
317
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
318
|
+
"expected publish settings toggle",
|
|
319
|
+
).click();
|
|
320
|
+
await el.updateComplete;
|
|
321
|
+
|
|
322
|
+
expect(el.textContent).toContain(
|
|
323
|
+
"Doesn't appear in Latest. Still appears in collections you add it to.",
|
|
324
|
+
);
|
|
325
|
+
expect(el.textContent).toContain("Only visible when signed in.");
|
|
153
326
|
});
|
|
154
327
|
|
|
155
328
|
it("format switching updates active state", async () => {
|
|
@@ -177,6 +350,9 @@ describe("JantComposeDialog", () => {
|
|
|
177
350
|
expect(noteBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
178
351
|
false,
|
|
179
352
|
);
|
|
353
|
+
expect(
|
|
354
|
+
el.querySelector('.compose-tool-btn-view[aria-label="Fullscreen"]'),
|
|
355
|
+
).toBeNull();
|
|
180
356
|
});
|
|
181
357
|
|
|
182
358
|
it("submit dispatches jant:compose-submit-deferred with correct payload", async () => {
|
|
@@ -218,80 +394,46 @@ describe("JantComposeDialog", () => {
|
|
|
218
394
|
expect(detail.status).toBe("published");
|
|
219
395
|
expect(detail.visibility).toBe("public");
|
|
220
396
|
expect(detail.collectionIds).toEqual([]);
|
|
221
|
-
expect(detail.
|
|
222
|
-
expect(detail.mediaAlts).toEqual({});
|
|
397
|
+
expect(detail.attachments).toEqual([]);
|
|
223
398
|
expect(detail.pendingAttachments).toEqual([]);
|
|
224
399
|
});
|
|
225
400
|
|
|
226
|
-
it("
|
|
401
|
+
it("submit omits a hidden rating while keeping the compose body", async () => {
|
|
227
402
|
const el = await createElement();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
232
|
-
"expected collection trigger",
|
|
233
|
-
);
|
|
234
|
-
trigger.click();
|
|
235
|
-
await el.updateComplete;
|
|
236
|
-
|
|
237
|
-
const options = el.querySelectorAll<HTMLElement>(
|
|
238
|
-
"[data-popover] [role='option']",
|
|
403
|
+
const editor = requireElement(
|
|
404
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
405
|
+
"expected compose editor",
|
|
239
406
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Deselect first
|
|
253
|
-
options[0].click();
|
|
254
|
-
await el.updateComplete;
|
|
255
|
-
expect(el._collectionIds).toEqual(["col-2"]);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("reset restores initial state", async () => {
|
|
259
|
-
const el = await createElement();
|
|
260
|
-
el._format = "link";
|
|
261
|
-
el._collectionIds = ["col-1", "col-2"];
|
|
262
|
-
el._loading = true;
|
|
263
|
-
el._draftSourceId = "abc123";
|
|
264
|
-
|
|
265
|
-
el.reset();
|
|
266
|
-
|
|
267
|
-
expect(el._format).toBe("note");
|
|
268
|
-
expect(el._collectionIds).toEqual([]);
|
|
269
|
-
expect(el._loading).toBe(false);
|
|
270
|
-
expect(el._draftSourceId).toBeNull();
|
|
271
|
-
});
|
|
407
|
+
editor._bodyJson = {
|
|
408
|
+
type: "doc",
|
|
409
|
+
content: [
|
|
410
|
+
{
|
|
411
|
+
type: "paragraph",
|
|
412
|
+
content: [{ type: "text", text: "Post without visible rating" }],
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
};
|
|
416
|
+
editor._rating = 3;
|
|
417
|
+
editor._showRating = false;
|
|
418
|
+
await editor.updateComplete;
|
|
272
419
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
420
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
421
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
422
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
423
|
+
});
|
|
277
424
|
|
|
278
|
-
|
|
425
|
+
requireElement(
|
|
279
426
|
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
280
427
|
"expected post button",
|
|
281
|
-
);
|
|
282
|
-
expect(postBtn.disabled).toBe(true);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("renders collection selector even without collections", async () => {
|
|
286
|
-
const el = await createElement([]);
|
|
428
|
+
).click();
|
|
287
429
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
expect(
|
|
430
|
+
expect(receivedDetail).not.toBeNull();
|
|
431
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail;
|
|
432
|
+
expect(detail.rating).toBe(0);
|
|
433
|
+
expect(detail.body).toContain("Post without visible rating");
|
|
292
434
|
});
|
|
293
435
|
|
|
294
|
-
it("
|
|
436
|
+
it("includes publish settings in the submit payload", async () => {
|
|
295
437
|
const el = await createElement();
|
|
296
438
|
const editor = requireElement(
|
|
297
439
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
@@ -302,431 +444,2437 @@ describe("JantComposeDialog", () => {
|
|
|
302
444
|
content: [
|
|
303
445
|
{
|
|
304
446
|
type: "paragraph",
|
|
305
|
-
content: [{ type: "text", text: "
|
|
447
|
+
content: [{ type: "text", text: "Configured post" }],
|
|
306
448
|
},
|
|
307
449
|
],
|
|
308
450
|
};
|
|
309
451
|
await editor.updateComplete;
|
|
310
452
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
453
|
+
requireElement(
|
|
454
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
455
|
+
"expected publish settings toggle",
|
|
456
|
+
).click();
|
|
457
|
+
await el.updateComplete;
|
|
458
|
+
|
|
459
|
+
const options = el.querySelectorAll<HTMLButtonElement>(
|
|
460
|
+
".compose-publish-option[role='radio']",
|
|
315
461
|
);
|
|
316
|
-
|
|
462
|
+
expect(options).toHaveLength(3);
|
|
463
|
+
options[1]?.click();
|
|
317
464
|
await el.updateComplete;
|
|
318
465
|
|
|
319
|
-
expect(
|
|
320
|
-
|
|
466
|
+
expect(
|
|
467
|
+
requireElement(
|
|
468
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
469
|
+
"expected publish button",
|
|
470
|
+
).textContent?.trim(),
|
|
471
|
+
).toBe("Post hidden");
|
|
472
|
+
|
|
473
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
474
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
475
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
requireElement(
|
|
479
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
480
|
+
"expected publish button",
|
|
481
|
+
).click();
|
|
482
|
+
|
|
483
|
+
expect(receivedDetail).not.toBeNull();
|
|
484
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).visibility).toBe(
|
|
485
|
+
"latest_hidden",
|
|
486
|
+
);
|
|
487
|
+
expect(
|
|
488
|
+
(receivedDetail as unknown as ComposeSubmitDetail).slug,
|
|
489
|
+
).toBeUndefined();
|
|
321
490
|
});
|
|
322
491
|
|
|
323
|
-
it("
|
|
492
|
+
it("updates the publish button label for private visibility", async () => {
|
|
324
493
|
const el = await createElement();
|
|
325
494
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}),
|
|
332
|
-
);
|
|
495
|
+
requireElement(
|
|
496
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
497
|
+
"expected publish settings toggle",
|
|
498
|
+
).click();
|
|
499
|
+
await el.updateComplete;
|
|
333
500
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
337
|
-
"expected draft button",
|
|
501
|
+
const options = el.querySelectorAll<HTMLButtonElement>(
|
|
502
|
+
".compose-publish-option[role='radio']",
|
|
338
503
|
);
|
|
339
|
-
|
|
504
|
+
expect(options).toHaveLength(3);
|
|
505
|
+
options[2]?.click();
|
|
340
506
|
await el.updateComplete;
|
|
341
507
|
|
|
342
|
-
expect(
|
|
508
|
+
expect(
|
|
509
|
+
requireElement(
|
|
510
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
511
|
+
"expected publish button",
|
|
512
|
+
).textContent?.trim(),
|
|
513
|
+
).toBe("Post privately");
|
|
514
|
+
});
|
|
343
515
|
|
|
344
|
-
|
|
345
|
-
|
|
516
|
+
it("updates the publish button label for hidden visibility", async () => {
|
|
517
|
+
const el = await createElement();
|
|
518
|
+
|
|
519
|
+
requireElement(
|
|
520
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
521
|
+
"expected publish settings toggle",
|
|
522
|
+
).click();
|
|
346
523
|
await el.updateComplete;
|
|
347
524
|
|
|
348
|
-
|
|
349
|
-
|
|
525
|
+
const options = el.querySelectorAll<HTMLButtonElement>(
|
|
526
|
+
".compose-publish-option[role='radio']",
|
|
527
|
+
);
|
|
528
|
+
expect(options).toHaveLength(3);
|
|
529
|
+
options[1]?.click();
|
|
530
|
+
await el.updateComplete;
|
|
350
531
|
|
|
351
|
-
|
|
532
|
+
expect(
|
|
533
|
+
requireElement(
|
|
534
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
535
|
+
"expected publish button",
|
|
536
|
+
).textContent?.trim(),
|
|
537
|
+
).toBe("Post hidden");
|
|
352
538
|
});
|
|
353
539
|
|
|
354
|
-
it("
|
|
540
|
+
it("opens a new post with the requested collection and keeps the last visibility until refresh", async () => {
|
|
355
541
|
const el = await createElement();
|
|
356
|
-
|
|
542
|
+
|
|
543
|
+
await el.openNew({ collectionId: "col-2", restoreDraft: false });
|
|
357
544
|
await el.updateComplete;
|
|
358
545
|
|
|
359
|
-
|
|
360
|
-
el.
|
|
361
|
-
dispatched = true;
|
|
362
|
-
});
|
|
546
|
+
expect(el._collectionIds).toEqual(["col-2"]);
|
|
547
|
+
expect(el._visibility).toBe("public");
|
|
363
548
|
|
|
364
|
-
|
|
365
|
-
el.querySelector<HTMLButtonElement>(".compose-publish-
|
|
366
|
-
"expected
|
|
549
|
+
requireElement(
|
|
550
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
551
|
+
"expected publish settings toggle",
|
|
552
|
+
).click();
|
|
553
|
+
await el.updateComplete;
|
|
554
|
+
|
|
555
|
+
const options = el.querySelectorAll<HTMLButtonElement>(
|
|
556
|
+
".compose-publish-option[role='radio']",
|
|
367
557
|
);
|
|
368
|
-
|
|
558
|
+
options[1]?.click();
|
|
559
|
+
await el.updateComplete;
|
|
369
560
|
|
|
370
|
-
expect(
|
|
371
|
-
});
|
|
561
|
+
expect(el._visibility).toBe("latest_hidden");
|
|
372
562
|
|
|
373
|
-
|
|
374
|
-
const el = await createElement();
|
|
375
|
-
el._loading = true;
|
|
563
|
+
el.reset();
|
|
376
564
|
await el.updateComplete;
|
|
377
565
|
|
|
378
|
-
|
|
379
|
-
|
|
566
|
+
expect(el._visibility).toBe("latest_hidden");
|
|
567
|
+
|
|
568
|
+
await el.openNew({ collectionId: "col-1", restoreDraft: false });
|
|
569
|
+
await el.updateComplete;
|
|
570
|
+
|
|
571
|
+
expect(el._collectionIds).toEqual(["col-1"]);
|
|
572
|
+
expect(el._visibility).toBe("latest_hidden");
|
|
380
573
|
});
|
|
381
574
|
|
|
382
|
-
it("
|
|
575
|
+
it("prepends the requested collection even when a local draft is restored", async () => {
|
|
383
576
|
const el = await createElement();
|
|
384
577
|
|
|
385
|
-
|
|
386
|
-
|
|
578
|
+
globalThis.localStorage.setItem(
|
|
579
|
+
"jant:compose-draft",
|
|
580
|
+
JSON.stringify({
|
|
581
|
+
format: "note",
|
|
582
|
+
title: "",
|
|
583
|
+
bodyJson: {
|
|
584
|
+
type: "doc",
|
|
585
|
+
content: [
|
|
586
|
+
{
|
|
587
|
+
type: "paragraph",
|
|
588
|
+
content: [{ type: "text", text: "Draft body" }],
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
},
|
|
592
|
+
url: "",
|
|
593
|
+
quoteText: "",
|
|
594
|
+
quoteAuthor: "",
|
|
595
|
+
slug: "",
|
|
596
|
+
visibility: "public",
|
|
597
|
+
rating: 0,
|
|
598
|
+
showTitle: false,
|
|
599
|
+
showRating: false,
|
|
600
|
+
collectionIds: ["col-1"],
|
|
601
|
+
attachedTexts: [],
|
|
602
|
+
attachmentOrder: [],
|
|
603
|
+
savedAt: Date.now(),
|
|
604
|
+
}),
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
await el.openNew({ collectionId: "col-2" });
|
|
608
|
+
await flushUpdates(el);
|
|
609
|
+
|
|
610
|
+
expect(el._collectionIds).toEqual(["col-2", "col-1"]);
|
|
387
611
|
});
|
|
388
612
|
|
|
389
|
-
it("
|
|
613
|
+
it("includes a custom slug from the publish settings panel in the submit payload", async () => {
|
|
614
|
+
mockSlugApi((url) => {
|
|
615
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
616
|
+
return { body: { slug: "configured-post" } };
|
|
617
|
+
}
|
|
618
|
+
if (url.searchParams.get("mode") === "check") {
|
|
619
|
+
return { body: { slug: "custom-link", available: true } };
|
|
620
|
+
}
|
|
621
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const el = await createElement();
|
|
625
|
+
const editor = requireElement(
|
|
626
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
627
|
+
"expected compose editor",
|
|
628
|
+
);
|
|
629
|
+
editor._bodyJson = {
|
|
630
|
+
type: "doc",
|
|
631
|
+
content: [
|
|
632
|
+
{
|
|
633
|
+
type: "paragraph",
|
|
634
|
+
content: [{ type: "text", text: "Configured post" }],
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
};
|
|
638
|
+
await editor.updateComplete;
|
|
639
|
+
|
|
640
|
+
const publishToggle = requireElement(
|
|
641
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
642
|
+
"expected publish settings toggle",
|
|
643
|
+
);
|
|
644
|
+
publishToggle.click();
|
|
645
|
+
await el.updateComplete;
|
|
646
|
+
await flushUpdates(el);
|
|
647
|
+
|
|
648
|
+
const slugInput = requireElement(
|
|
649
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
650
|
+
"expected custom link input",
|
|
651
|
+
);
|
|
652
|
+
slugInput.value = "custom-link";
|
|
653
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
654
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
655
|
+
await el.updateComplete;
|
|
656
|
+
|
|
657
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
658
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
659
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
requireElement(
|
|
663
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
664
|
+
"expected publish button",
|
|
665
|
+
).click();
|
|
666
|
+
|
|
667
|
+
expect(receivedDetail).not.toBeNull();
|
|
668
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).slug).toBe(
|
|
669
|
+
"custom-link",
|
|
670
|
+
);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it("reopens the publish settings panel with the custom link field intact", async () => {
|
|
674
|
+
mockSlugApi((url) => {
|
|
675
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
676
|
+
return { body: { slug: "reading-notes" } };
|
|
677
|
+
}
|
|
678
|
+
if (url.searchParams.get("mode") === "check") {
|
|
679
|
+
return { body: { slug: "reading-notes", available: true } };
|
|
680
|
+
}
|
|
681
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const el = await createElement();
|
|
685
|
+
|
|
686
|
+
const publishToggle = requireElement(
|
|
687
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
688
|
+
"expected publish settings toggle",
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
publishToggle.click();
|
|
692
|
+
await el.updateComplete;
|
|
693
|
+
await flushUpdates(el);
|
|
694
|
+
|
|
695
|
+
const slugInput = requireElement(
|
|
696
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
697
|
+
"expected custom link input",
|
|
698
|
+
);
|
|
699
|
+
slugInput.value = "reading-notes";
|
|
700
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
701
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
702
|
+
await el.updateComplete;
|
|
703
|
+
|
|
704
|
+
publishToggle.click();
|
|
705
|
+
await el.updateComplete;
|
|
706
|
+
publishToggle.click();
|
|
707
|
+
await el.updateComplete;
|
|
708
|
+
|
|
709
|
+
expect(el.querySelector(".compose-publish-slug-input")).not.toBeNull();
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it("shows a slug error and blocks publish when the custom link is invalid", async () => {
|
|
713
|
+
mockSlugApi(() => ({ body: { slug: "hello-world" } }));
|
|
714
|
+
|
|
715
|
+
const el = await createElement();
|
|
716
|
+
const editor = requireElement(
|
|
717
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
718
|
+
"expected compose editor",
|
|
719
|
+
);
|
|
720
|
+
editor._bodyJson = {
|
|
721
|
+
type: "doc",
|
|
722
|
+
content: [
|
|
723
|
+
{ type: "paragraph", content: [{ type: "text", text: "Hello world" }] },
|
|
724
|
+
],
|
|
725
|
+
};
|
|
726
|
+
await editor.updateComplete;
|
|
727
|
+
|
|
728
|
+
const publishToggle = requireElement(
|
|
729
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
730
|
+
"expected publish settings toggle",
|
|
731
|
+
);
|
|
732
|
+
publishToggle.click();
|
|
733
|
+
await el.updateComplete;
|
|
734
|
+
await flushUpdates(el);
|
|
735
|
+
|
|
736
|
+
const slugInput = requireElement(
|
|
737
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
738
|
+
"expected custom link input",
|
|
739
|
+
);
|
|
740
|
+
slugInput.value = "bad/slug";
|
|
741
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
742
|
+
await el.updateComplete;
|
|
743
|
+
|
|
744
|
+
expect(
|
|
745
|
+
requireElement(
|
|
746
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
747
|
+
"expected publish button",
|
|
748
|
+
).disabled,
|
|
749
|
+
).toBe(true);
|
|
750
|
+
expect(
|
|
751
|
+
el.querySelector("[data-compose-slug-error]")?.textContent?.trim(),
|
|
752
|
+
).toBe("Use lowercase letters, numbers, and hyphens only.");
|
|
753
|
+
|
|
754
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
755
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
756
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
requireElement(
|
|
760
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
761
|
+
"expected publish button",
|
|
762
|
+
).click();
|
|
763
|
+
|
|
764
|
+
expect(receivedDetail).toBeNull();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("shows a suggested slug without submitting it by default", async () => {
|
|
768
|
+
mockSlugApi((url) => {
|
|
769
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
770
|
+
return { body: { slug: "hello-world" } };
|
|
771
|
+
}
|
|
772
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const el = await createElement();
|
|
776
|
+
const editor = requireElement(
|
|
777
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
778
|
+
"expected compose editor",
|
|
779
|
+
);
|
|
780
|
+
editor._showTitle = true;
|
|
781
|
+
editor._title = "Hello World";
|
|
782
|
+
editor._bodyJson = {
|
|
783
|
+
type: "doc",
|
|
784
|
+
content: [
|
|
785
|
+
{ type: "paragraph", content: [{ type: "text", text: "Hello world" }] },
|
|
786
|
+
],
|
|
787
|
+
};
|
|
788
|
+
await editor.updateComplete;
|
|
789
|
+
|
|
790
|
+
const publishToggle = requireElement(
|
|
791
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
792
|
+
"expected publish settings toggle",
|
|
793
|
+
);
|
|
794
|
+
publishToggle.click();
|
|
795
|
+
await el.updateComplete;
|
|
796
|
+
await flushUpdates(el);
|
|
797
|
+
|
|
798
|
+
expect(
|
|
799
|
+
el.querySelector(".compose-slug-suggestion-value")?.textContent?.trim(),
|
|
800
|
+
).toBe("/hello-world");
|
|
801
|
+
|
|
802
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
803
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
804
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
requireElement(
|
|
808
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
809
|
+
"expected publish button",
|
|
810
|
+
).click();
|
|
811
|
+
|
|
812
|
+
expect(receivedDetail).not.toBeNull();
|
|
813
|
+
expect(
|
|
814
|
+
(receivedDetail as unknown as ComposeSubmitDetail).slug,
|
|
815
|
+
).toBeUndefined();
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("shows visibility and custom link settings in the publish panel", async () => {
|
|
819
|
+
const el = await createElement();
|
|
820
|
+
|
|
821
|
+
const publishToggle = requireElement(
|
|
822
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
823
|
+
"expected publish settings toggle",
|
|
824
|
+
);
|
|
825
|
+
publishToggle.click();
|
|
826
|
+
await el.updateComplete;
|
|
827
|
+
|
|
828
|
+
const panel = requireElement(
|
|
829
|
+
el.querySelector<HTMLElement>("[data-compose-publish-panel]"),
|
|
830
|
+
"expected publish settings panel",
|
|
831
|
+
);
|
|
832
|
+
expect(panel.textContent).not.toContain("Publish settings");
|
|
833
|
+
expect(panel.textContent).toContain("Visibility");
|
|
834
|
+
expect(panel.textContent).toContain("Custom link");
|
|
835
|
+
expect(panel.textContent).not.toContain("Save as draft");
|
|
836
|
+
expect(panel.textContent).not.toContain("Discard");
|
|
837
|
+
expect(panel.querySelector(".compose-publish-slug-input")).not.toBeNull();
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it("clears the custom slug with the reset action", async () => {
|
|
841
|
+
mockSlugApi((url) => {
|
|
842
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
843
|
+
return { body: { slug: "hello-world" } };
|
|
844
|
+
}
|
|
845
|
+
if (url.searchParams.get("mode") === "check") {
|
|
846
|
+
return { body: { slug: "manual-link", available: true } };
|
|
847
|
+
}
|
|
848
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
const el = await createElement();
|
|
852
|
+
const publishToggle = requireElement(
|
|
853
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
854
|
+
"expected publish settings toggle",
|
|
855
|
+
);
|
|
856
|
+
publishToggle.click();
|
|
857
|
+
await el.updateComplete;
|
|
858
|
+
await flushUpdates(el);
|
|
859
|
+
|
|
860
|
+
const slugInput = requireElement(
|
|
861
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
862
|
+
"expected custom link input",
|
|
863
|
+
);
|
|
864
|
+
slugInput.value = "manual-link";
|
|
865
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
866
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
867
|
+
await el.updateComplete;
|
|
868
|
+
|
|
869
|
+
requireElement(
|
|
870
|
+
Array.from(
|
|
871
|
+
el.querySelectorAll<HTMLButtonElement>(
|
|
872
|
+
".compose-publish-section-action",
|
|
873
|
+
),
|
|
874
|
+
).find((button) => button.textContent?.includes("Reset link")) ?? null,
|
|
875
|
+
"expected reset action",
|
|
876
|
+
).click();
|
|
877
|
+
await flushUpdates(el);
|
|
878
|
+
|
|
879
|
+
expect(
|
|
880
|
+
requireElement(
|
|
881
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
882
|
+
"expected custom link input",
|
|
883
|
+
).value,
|
|
884
|
+
).toBe("");
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it("does not request or show a suggested slug when no title is available", async () => {
|
|
888
|
+
const fetchSpy = vi
|
|
889
|
+
.spyOn(globalThis, "fetch")
|
|
890
|
+
.mockImplementation(async () => {
|
|
891
|
+
throw new Error(
|
|
892
|
+
"slug suggestion should not be requested without a title",
|
|
893
|
+
);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const el = await createElement();
|
|
897
|
+
el._format = "quote";
|
|
898
|
+
await el.updateComplete;
|
|
899
|
+
|
|
900
|
+
const publishToggle = requireElement(
|
|
901
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
902
|
+
"expected publish settings toggle",
|
|
903
|
+
);
|
|
904
|
+
publishToggle.click();
|
|
905
|
+
await el.updateComplete;
|
|
906
|
+
await flushUpdates(el);
|
|
907
|
+
|
|
908
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
909
|
+
expect(el.querySelector(".compose-slug-suggestion")).toBeNull();
|
|
910
|
+
expect(el.querySelector(".compose-publish-slug-status")).toBeNull();
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it("refreshes the suggested slug when the title changes", async () => {
|
|
914
|
+
vi.useFakeTimers();
|
|
915
|
+
mockSlugApi((url) => {
|
|
916
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
917
|
+
const title = url.searchParams.get("title");
|
|
918
|
+
return {
|
|
919
|
+
body: {
|
|
920
|
+
slug: title === "Updated Title" ? "updated-title" : "hello-world",
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
try {
|
|
928
|
+
const el = await createElement();
|
|
929
|
+
const editor = requireElement(
|
|
930
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
931
|
+
"expected compose editor",
|
|
932
|
+
);
|
|
933
|
+
editor._showTitle = true;
|
|
934
|
+
editor._title = "Hello World";
|
|
935
|
+
await editor.updateComplete;
|
|
936
|
+
|
|
937
|
+
const publishToggle = requireElement(
|
|
938
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
939
|
+
"expected publish settings toggle",
|
|
940
|
+
);
|
|
941
|
+
publishToggle.click();
|
|
942
|
+
await el.updateComplete;
|
|
943
|
+
await flushUpdates(el);
|
|
944
|
+
|
|
945
|
+
expect(
|
|
946
|
+
el.querySelector(".compose-slug-suggestion-value")?.textContent?.trim(),
|
|
947
|
+
).toBe("/hello-world");
|
|
948
|
+
|
|
949
|
+
editor._title = "Updated Title";
|
|
950
|
+
el.dispatchEvent(
|
|
951
|
+
new CustomEvent("jant:compose-content-changed", { bubbles: true }),
|
|
952
|
+
);
|
|
953
|
+
await vi.runAllTimersAsync();
|
|
954
|
+
await flushUpdates(el);
|
|
955
|
+
|
|
956
|
+
expect(
|
|
957
|
+
el.querySelector(".compose-slug-suggestion-value")?.textContent?.trim(),
|
|
958
|
+
).toBe("/updated-title");
|
|
959
|
+
} finally {
|
|
960
|
+
vi.useRealTimers();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it("shows an async error when the manual custom link is already taken", async () => {
|
|
965
|
+
vi.useFakeTimers();
|
|
966
|
+
mockSlugApi((url) => {
|
|
967
|
+
if (url.searchParams.get("mode") === "suggest") {
|
|
968
|
+
return { body: { slug: "hello-world" } };
|
|
969
|
+
}
|
|
970
|
+
if (url.searchParams.get("mode") === "check") {
|
|
971
|
+
return { body: { slug: "taken-link", available: false } };
|
|
972
|
+
}
|
|
973
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
try {
|
|
977
|
+
const el = await createElement();
|
|
978
|
+
const editor = requireElement(
|
|
979
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
980
|
+
"expected compose editor",
|
|
981
|
+
);
|
|
982
|
+
editor._bodyJson = {
|
|
983
|
+
type: "doc",
|
|
984
|
+
content: [
|
|
985
|
+
{
|
|
986
|
+
type: "paragraph",
|
|
987
|
+
content: [{ type: "text", text: "Configured post" }],
|
|
988
|
+
},
|
|
989
|
+
],
|
|
990
|
+
};
|
|
991
|
+
await editor.updateComplete;
|
|
992
|
+
|
|
993
|
+
const publishToggle = requireElement(
|
|
994
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
995
|
+
"expected publish settings toggle",
|
|
996
|
+
);
|
|
997
|
+
publishToggle.click();
|
|
998
|
+
await el.updateComplete;
|
|
999
|
+
await flushUpdates(el);
|
|
1000
|
+
|
|
1001
|
+
const slugInput = requireElement(
|
|
1002
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
1003
|
+
"expected custom link input",
|
|
1004
|
+
);
|
|
1005
|
+
slugInput.value = "taken-link";
|
|
1006
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1007
|
+
await vi.runAllTimersAsync();
|
|
1008
|
+
await el.updateComplete;
|
|
1009
|
+
|
|
1010
|
+
expect(
|
|
1011
|
+
el.querySelector("[data-compose-slug-error]")?.textContent?.trim(),
|
|
1012
|
+
).toBe("This link is already in use. Choose something else.");
|
|
1013
|
+
expect(
|
|
1014
|
+
requireElement(
|
|
1015
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1016
|
+
"expected publish button",
|
|
1017
|
+
).disabled,
|
|
1018
|
+
).toBe(true);
|
|
1019
|
+
} finally {
|
|
1020
|
+
vi.useRealTimers();
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
it("keeps publish enabled and hides checking status while the custom link check is pending", async () => {
|
|
1025
|
+
vi.useFakeTimers();
|
|
1026
|
+
mockSlugApi((url) => {
|
|
1027
|
+
if (url.searchParams.get("mode") === "check") {
|
|
1028
|
+
return { body: { slug: "pending-link", available: true } };
|
|
1029
|
+
}
|
|
1030
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
try {
|
|
1034
|
+
const el = await createElement();
|
|
1035
|
+
const editor = requireElement(
|
|
1036
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1037
|
+
"expected compose editor",
|
|
1038
|
+
);
|
|
1039
|
+
editor._bodyJson = {
|
|
1040
|
+
type: "doc",
|
|
1041
|
+
content: [
|
|
1042
|
+
{
|
|
1043
|
+
type: "paragraph",
|
|
1044
|
+
content: [{ type: "text", text: "Configured post" }],
|
|
1045
|
+
},
|
|
1046
|
+
],
|
|
1047
|
+
};
|
|
1048
|
+
await editor.updateComplete;
|
|
1049
|
+
|
|
1050
|
+
requireElement(
|
|
1051
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
1052
|
+
"expected publish settings toggle",
|
|
1053
|
+
).click();
|
|
1054
|
+
await el.updateComplete;
|
|
1055
|
+
|
|
1056
|
+
const slugInput = requireElement(
|
|
1057
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
1058
|
+
"expected custom link input",
|
|
1059
|
+
);
|
|
1060
|
+
slugInput.value = "pending-link";
|
|
1061
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1062
|
+
await el.updateComplete;
|
|
1063
|
+
|
|
1064
|
+
expect(el._slugCheckLoading).toBe(true);
|
|
1065
|
+
expect(
|
|
1066
|
+
requireElement(
|
|
1067
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1068
|
+
"expected publish button",
|
|
1069
|
+
).disabled,
|
|
1070
|
+
).toBe(false);
|
|
1071
|
+
expect(el.textContent).not.toContain("Checking link...");
|
|
1072
|
+
expect(el.querySelector("[data-compose-slug-error]")).toBeNull();
|
|
1073
|
+
} finally {
|
|
1074
|
+
vi.useRealTimers();
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
it("shows the final post link preview while editing a valid custom link", async () => {
|
|
1079
|
+
mockSlugApi((url) => {
|
|
1080
|
+
if (url.searchParams.get("mode") === "check") {
|
|
1081
|
+
return { body: { slug: "final-link", available: true } };
|
|
1082
|
+
}
|
|
1083
|
+
throw new Error(`Unexpected slug mode: ${url.search}`);
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
const el = await createElement();
|
|
1087
|
+
const editor = requireElement(
|
|
1088
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1089
|
+
"expected compose editor",
|
|
1090
|
+
);
|
|
1091
|
+
editor._bodyJson = {
|
|
1092
|
+
type: "doc",
|
|
1093
|
+
content: [
|
|
1094
|
+
{
|
|
1095
|
+
type: "paragraph",
|
|
1096
|
+
content: [{ type: "text", text: "Configured post" }],
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
};
|
|
1100
|
+
await editor.updateComplete;
|
|
1101
|
+
|
|
1102
|
+
requireElement(
|
|
1103
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-toggle"),
|
|
1104
|
+
"expected publish settings toggle",
|
|
1105
|
+
).click();
|
|
1106
|
+
await el.updateComplete;
|
|
1107
|
+
|
|
1108
|
+
const slugInput = requireElement(
|
|
1109
|
+
el.querySelector<HTMLInputElement>(".compose-publish-slug-input"),
|
|
1110
|
+
"expected custom link input",
|
|
1111
|
+
);
|
|
1112
|
+
slugInput.value = "final-link";
|
|
1113
|
+
slugInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1114
|
+
await el.updateComplete;
|
|
1115
|
+
|
|
1116
|
+
expect(
|
|
1117
|
+
el.querySelector("[data-compose-slug-preview]")?.textContent?.trim(),
|
|
1118
|
+
).toBe(`${globalThis.location.origin}/final-link`);
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
it("includes the thread root id when replying", async () => {
|
|
1122
|
+
const el = await createElement();
|
|
1123
|
+
await el.openReply(
|
|
1124
|
+
"019ce8ce-d6d8-7fda-a5df-c2da2bef5ade",
|
|
1125
|
+
{
|
|
1126
|
+
contentHtml: "<p>Parent</p>",
|
|
1127
|
+
dateText: "Mar 14",
|
|
1128
|
+
},
|
|
1129
|
+
"019ce8cf-19a1-7d16-9a75-017a9ac7299d",
|
|
1130
|
+
{
|
|
1131
|
+
kind: "timeline-item",
|
|
1132
|
+
id: "019ce8cf-19a1-7d16-9a75-017a9ac7299d",
|
|
1133
|
+
},
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
const editor = requireElement(
|
|
1137
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1138
|
+
"expected compose editor",
|
|
1139
|
+
);
|
|
1140
|
+
editor._bodyJson = {
|
|
1141
|
+
type: "doc",
|
|
1142
|
+
content: [
|
|
1143
|
+
{ type: "paragraph", content: [{ type: "text", text: "Reply body" }] },
|
|
1144
|
+
],
|
|
1145
|
+
};
|
|
1146
|
+
await editor.updateComplete;
|
|
1147
|
+
await el.updateComplete;
|
|
1148
|
+
|
|
1149
|
+
let receivedDetail:
|
|
1150
|
+
| (ComposeSubmitDetail & { pendingAttachments: unknown[] })
|
|
1151
|
+
| null = null;
|
|
1152
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
1153
|
+
const customEvent = event as CustomEvent<
|
|
1154
|
+
ComposeSubmitDetail & { pendingAttachments: unknown[] }
|
|
1155
|
+
>;
|
|
1156
|
+
receivedDetail = customEvent.detail;
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
requireElement(
|
|
1160
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1161
|
+
"expected reply button",
|
|
1162
|
+
).click();
|
|
1163
|
+
|
|
1164
|
+
expect(receivedDetail).not.toBeNull();
|
|
1165
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail & {
|
|
1166
|
+
pendingAttachments: unknown[];
|
|
1167
|
+
};
|
|
1168
|
+
expect(detail.replyToId).toBe("019ce8ce-d6d8-7fda-a5df-c2da2bef5ade");
|
|
1169
|
+
expect(detail.replyThreadRootId).toBe(
|
|
1170
|
+
"019ce8cf-19a1-7d16-9a75-017a9ac7299d",
|
|
1171
|
+
);
|
|
1172
|
+
expect(detail.replyRefreshKind).toBe("timeline-item");
|
|
1173
|
+
expect(detail.replyRefreshId).toBe("019ce8cf-19a1-7d16-9a75-017a9ac7299d");
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it("omits visibility from locked edit submissions", async () => {
|
|
1177
|
+
const el = await createElement();
|
|
1178
|
+
const editor = requireElement(
|
|
1179
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1180
|
+
"expected compose editor",
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
el._editPostId = "post-123";
|
|
1184
|
+
el._visibilityLocked = true;
|
|
1185
|
+
el._slug = "reply-note";
|
|
1186
|
+
editor._bodyJson = {
|
|
1187
|
+
type: "doc",
|
|
1188
|
+
content: [
|
|
1189
|
+
{
|
|
1190
|
+
type: "paragraph",
|
|
1191
|
+
content: [{ type: "text", text: "Edited reply" }],
|
|
1192
|
+
},
|
|
1193
|
+
],
|
|
1194
|
+
};
|
|
1195
|
+
await editor.updateComplete;
|
|
1196
|
+
await el.updateComplete;
|
|
1197
|
+
|
|
1198
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
1199
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
1200
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
requireElement(
|
|
1204
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1205
|
+
"expected publish button",
|
|
1206
|
+
).click();
|
|
1207
|
+
|
|
1208
|
+
expect(receivedDetail).not.toBeNull();
|
|
1209
|
+
expect(
|
|
1210
|
+
(receivedDetail as unknown as ComposeSubmitDetail).visibility,
|
|
1211
|
+
).toBeUndefined();
|
|
1212
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).slug).toBe(
|
|
1213
|
+
"reply-note",
|
|
1214
|
+
);
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("collection selector toggles IDs", async () => {
|
|
1218
|
+
const el = await createElement();
|
|
1219
|
+
|
|
1220
|
+
// Open collection combobox
|
|
1221
|
+
const trigger = requireElement(
|
|
1222
|
+
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
1223
|
+
"expected collection trigger",
|
|
1224
|
+
);
|
|
1225
|
+
trigger.click();
|
|
1226
|
+
await el.updateComplete;
|
|
1227
|
+
|
|
1228
|
+
const options = el.querySelectorAll<HTMLElement>(
|
|
1229
|
+
"[data-popover] [role='option']",
|
|
1230
|
+
);
|
|
1231
|
+
expect(options.length).toBe(2);
|
|
1232
|
+
|
|
1233
|
+
// Select first collection
|
|
1234
|
+
options[0].click();
|
|
1235
|
+
await el.updateComplete;
|
|
1236
|
+
expect(el._collectionIds).toEqual(["col-1"]);
|
|
1237
|
+
expect(
|
|
1238
|
+
el.querySelector(".compose-collection-label")?.textContent?.trim(),
|
|
1239
|
+
).toBe("Books");
|
|
1240
|
+
expect(
|
|
1241
|
+
options[0]?.querySelector(".compose-collection-option-label"),
|
|
1242
|
+
).not.toBeNull();
|
|
1243
|
+
|
|
1244
|
+
// Select second collection
|
|
1245
|
+
options[1].click();
|
|
1246
|
+
await el.updateComplete;
|
|
1247
|
+
expect(el._collectionIds).toEqual(["col-1", "col-2"]);
|
|
1248
|
+
|
|
1249
|
+
// Deselect first
|
|
1250
|
+
options[0].click();
|
|
1251
|
+
await el.updateComplete;
|
|
1252
|
+
expect(el._collectionIds).toEqual(["col-2"]);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
it("reset restores initial state", async () => {
|
|
1256
|
+
const el = await createElement();
|
|
1257
|
+
el._format = "link";
|
|
1258
|
+
el._collectionIds = ["col-1", "col-2"];
|
|
1259
|
+
el._loading = true;
|
|
1260
|
+
el._draftSourceId = "abc123";
|
|
1261
|
+
|
|
1262
|
+
el.reset();
|
|
1263
|
+
|
|
1264
|
+
expect(el._format).toBe("note");
|
|
1265
|
+
expect(el._collectionIds).toEqual([]);
|
|
1266
|
+
expect(el._loading).toBe(false);
|
|
1267
|
+
expect(el._draftSourceId).toBeNull();
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it("loading state disables submit button", async () => {
|
|
1271
|
+
const el = await createElement();
|
|
1272
|
+
el._loading = true;
|
|
1273
|
+
await el.updateComplete;
|
|
1274
|
+
|
|
1275
|
+
const postBtn = requireElement(
|
|
1276
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1277
|
+
"expected post button",
|
|
1278
|
+
);
|
|
1279
|
+
expect(postBtn.disabled).toBe(true);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it("renders collection selector even without collections", async () => {
|
|
1283
|
+
const el = await createElement([]);
|
|
1284
|
+
|
|
1285
|
+
// Collection trigger is still shown so users can create new collections
|
|
1286
|
+
expect(el.querySelector(".compose-collection-trigger")).not.toBeNull();
|
|
1287
|
+
const actionRow = el.querySelector(".compose-action-row");
|
|
1288
|
+
expect(actionRow).not.toBeNull();
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
it("refreshes collections from the compose-sorted endpoint", async () => {
|
|
1292
|
+
const el = await createElement([{ id: "col-1", title: "Books" }]);
|
|
1293
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
1294
|
+
new Response(
|
|
1295
|
+
JSON.stringify({
|
|
1296
|
+
collections: [
|
|
1297
|
+
{ id: "col-2", title: "Movies" },
|
|
1298
|
+
{ id: "col-1", title: "Books" },
|
|
1299
|
+
],
|
|
1300
|
+
}),
|
|
1301
|
+
{
|
|
1302
|
+
status: 200,
|
|
1303
|
+
headers: { "Content-Type": "application/json" },
|
|
1304
|
+
},
|
|
1305
|
+
),
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
const refreshed = await el.refreshCollections();
|
|
1309
|
+
|
|
1310
|
+
expect(refreshed).toBe(true);
|
|
1311
|
+
expect(fetchSpy).toHaveBeenCalledWith("/api/collections?view=compose", {
|
|
1312
|
+
headers: { Accept: "application/json" },
|
|
1313
|
+
});
|
|
1314
|
+
expect(el.collections).toEqual([
|
|
1315
|
+
{ id: "col-2", title: "Movies" },
|
|
1316
|
+
{ id: "col-1", title: "Books" },
|
|
1317
|
+
]);
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
it("opens a quick collection dialog from the collection selector", async () => {
|
|
1321
|
+
const el = await createElement();
|
|
1322
|
+
const trigger = requireElement(
|
|
1323
|
+
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
1324
|
+
"expected collection trigger",
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
trigger.click();
|
|
1328
|
+
await el.updateComplete;
|
|
1329
|
+
|
|
1330
|
+
const addAction = requireElement(
|
|
1331
|
+
el.querySelector<HTMLElement>(".compose-collection-add-action"),
|
|
1332
|
+
"expected add collection action",
|
|
1333
|
+
);
|
|
1334
|
+
addAction.click();
|
|
1335
|
+
await el.updateComplete;
|
|
1336
|
+
|
|
1337
|
+
const composeInner = requireElement(
|
|
1338
|
+
el.querySelector<HTMLElement>(".compose-dialog-inner"),
|
|
1339
|
+
"expected compose dialog inner",
|
|
1340
|
+
);
|
|
1341
|
+
expect(el.querySelector("[data-collection-quick-dialog]")).not.toBeNull();
|
|
1342
|
+
expect(
|
|
1343
|
+
composeInner.classList.contains("compose-dialog-inner-suspended"),
|
|
1344
|
+
).toBe(true);
|
|
1345
|
+
expect(composeInner.getAttribute("aria-hidden")).toBe("true");
|
|
1346
|
+
expect(
|
|
1347
|
+
el.querySelector("[data-collection-quick-dialog] textarea"),
|
|
1348
|
+
).toBeNull();
|
|
1349
|
+
expect(
|
|
1350
|
+
el.querySelector("[data-collection-quick-dialog] select"),
|
|
1351
|
+
).toBeNull();
|
|
1352
|
+
expect(
|
|
1353
|
+
el.querySelector("[data-collection-quick-dialog] [data-icon-trigger]"),
|
|
1354
|
+
).toBeNull();
|
|
1355
|
+
expect(
|
|
1356
|
+
el.querySelector(
|
|
1357
|
+
"[data-collection-quick-dialog] [data-collection-slug-input]",
|
|
1358
|
+
),
|
|
1359
|
+
).toBeNull();
|
|
1360
|
+
expect(
|
|
1361
|
+
el.querySelector(
|
|
1362
|
+
"[data-collection-quick-dialog] .collection-quick-dialog-cancel",
|
|
1363
|
+
)?.textContent,
|
|
1364
|
+
).toContain("Cancel");
|
|
1365
|
+
expect(
|
|
1366
|
+
el.querySelector(
|
|
1367
|
+
"[data-collection-quick-dialog] .collection-quick-dialog-submit",
|
|
1368
|
+
)?.textContent,
|
|
1369
|
+
).toContain("Done");
|
|
1370
|
+
expect(el.textContent).toContain(
|
|
1371
|
+
"More options are available after you create it.",
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
requireElement(
|
|
1375
|
+
el.querySelector<HTMLButtonElement>(
|
|
1376
|
+
"[data-collection-quick-dialog] .collection-quick-dialog-cancel",
|
|
1377
|
+
),
|
|
1378
|
+
"expected quick dialog cancel button",
|
|
1379
|
+
).click();
|
|
1380
|
+
await flushUpdates(el);
|
|
1381
|
+
|
|
1382
|
+
expect(el.querySelector("[data-collection-quick-dialog]")).toBeNull();
|
|
1383
|
+
expect(
|
|
1384
|
+
composeInner.classList.contains("compose-dialog-inner-suspended"),
|
|
1385
|
+
).toBe(false);
|
|
1386
|
+
expect(composeInner.getAttribute("aria-hidden")).toBe("false");
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
it("restores compose after creating a collection from the quick dialog", async () => {
|
|
1390
|
+
const el = await createElement();
|
|
1391
|
+
const trigger = requireElement(
|
|
1392
|
+
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
1393
|
+
"expected collection trigger",
|
|
1394
|
+
);
|
|
1395
|
+
|
|
1396
|
+
trigger.click();
|
|
1397
|
+
await el.updateComplete;
|
|
1398
|
+
|
|
1399
|
+
requireElement(
|
|
1400
|
+
el.querySelector<HTMLElement>(".compose-collection-add-action"),
|
|
1401
|
+
"expected add collection action",
|
|
1402
|
+
).click();
|
|
1403
|
+
await el.updateComplete;
|
|
1404
|
+
|
|
1405
|
+
const fetchSpy = vi
|
|
1406
|
+
.spyOn(globalThis, "fetch")
|
|
1407
|
+
.mockImplementation(async (input, init) => {
|
|
1408
|
+
const raw =
|
|
1409
|
+
typeof input === "string"
|
|
1410
|
+
? input
|
|
1411
|
+
: input instanceof URL
|
|
1412
|
+
? input.toString()
|
|
1413
|
+
: input.url;
|
|
1414
|
+
const url = new URL(raw, "http://localhost");
|
|
1415
|
+
const method =
|
|
1416
|
+
init?.method ??
|
|
1417
|
+
(typeof input === "string" || input instanceof URL
|
|
1418
|
+
? "GET"
|
|
1419
|
+
: input.method);
|
|
1420
|
+
|
|
1421
|
+
if (url.pathname === "/api/collections" && method === "POST") {
|
|
1422
|
+
return new Response(
|
|
1423
|
+
JSON.stringify({ id: "col-3", title: "Travel" }),
|
|
1424
|
+
{
|
|
1425
|
+
status: 200,
|
|
1426
|
+
headers: { "Content-Type": "application/json" },
|
|
1427
|
+
},
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (
|
|
1432
|
+
url.pathname === "/api/collections" &&
|
|
1433
|
+
url.searchParams.get("view") === "compose"
|
|
1434
|
+
) {
|
|
1435
|
+
return new Response(
|
|
1436
|
+
JSON.stringify({
|
|
1437
|
+
collections: [
|
|
1438
|
+
{ id: "col-3", title: "Travel" },
|
|
1439
|
+
{ id: "col-1", title: "Books" },
|
|
1440
|
+
{ id: "col-2", title: "Movies" },
|
|
1441
|
+
],
|
|
1442
|
+
}),
|
|
1443
|
+
{
|
|
1444
|
+
status: 200,
|
|
1445
|
+
headers: { "Content-Type": "application/json" },
|
|
1446
|
+
},
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
throw new Error(`Unexpected fetch: ${url.pathname}${url.search}`);
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
const form = requireElement(
|
|
1454
|
+
el.querySelector<HTMLElement>(
|
|
1455
|
+
"[data-collection-quick-dialog] jant-collection-form",
|
|
1456
|
+
),
|
|
1457
|
+
"expected quick collection form",
|
|
1458
|
+
);
|
|
1459
|
+
form.dispatchEvent(
|
|
1460
|
+
new CustomEvent("jant:collection-submit", {
|
|
1461
|
+
bubbles: true,
|
|
1462
|
+
detail: {
|
|
1463
|
+
data: {
|
|
1464
|
+
title: "Travel",
|
|
1465
|
+
slug: "travel",
|
|
1466
|
+
description: "",
|
|
1467
|
+
sortOrder: "newest",
|
|
1468
|
+
icon: "",
|
|
1469
|
+
},
|
|
1470
|
+
},
|
|
1471
|
+
}),
|
|
1472
|
+
);
|
|
1473
|
+
|
|
1474
|
+
await vi.waitFor(() => {
|
|
1475
|
+
expect(el.querySelector("[data-collection-quick-dialog]")).toBeNull();
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
const composeInner = requireElement(
|
|
1479
|
+
el.querySelector<HTMLElement>(".compose-dialog-inner"),
|
|
1480
|
+
"expected compose dialog inner",
|
|
1481
|
+
);
|
|
1482
|
+
expect(fetchSpy).toHaveBeenCalled();
|
|
1483
|
+
expect(el.querySelector("[data-collection-quick-dialog]")).toBeNull();
|
|
1484
|
+
expect(
|
|
1485
|
+
composeInner.classList.contains("compose-dialog-inner-suspended"),
|
|
1486
|
+
).toBe(false);
|
|
1487
|
+
expect(el._collectionIds).toContain("col-3");
|
|
1488
|
+
expect(el.collections).toEqual([
|
|
1489
|
+
{ id: "col-3", title: "Travel" },
|
|
1490
|
+
{ id: "col-1", title: "Books" },
|
|
1491
|
+
{ id: "col-2", title: "Movies" },
|
|
1492
|
+
]);
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
it("draft button with content shows confirm panel", async () => {
|
|
1496
|
+
const el = await createElement();
|
|
1497
|
+
const editor = requireElement(
|
|
1498
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1499
|
+
"expected compose editor",
|
|
1500
|
+
);
|
|
1501
|
+
editor._bodyJson = {
|
|
1502
|
+
type: "doc",
|
|
1503
|
+
content: [
|
|
1504
|
+
{
|
|
1505
|
+
type: "paragraph",
|
|
1506
|
+
content: [{ type: "text", text: "Draft content" }],
|
|
1507
|
+
},
|
|
1508
|
+
],
|
|
1509
|
+
};
|
|
1510
|
+
await editor.updateComplete;
|
|
1511
|
+
|
|
1512
|
+
// Click the draft header button — should show confirm panel
|
|
1513
|
+
const draftBtn = requireElement(
|
|
1514
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
1515
|
+
"expected draft button",
|
|
1516
|
+
);
|
|
1517
|
+
draftBtn.click();
|
|
1518
|
+
await el.updateComplete;
|
|
1519
|
+
|
|
1520
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
1521
|
+
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it("draft button without content opens drafts panel", async () => {
|
|
1525
|
+
const el = await createElement();
|
|
1526
|
+
|
|
1527
|
+
// Mock fetch for drafts list
|
|
1528
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
1529
|
+
new Response(JSON.stringify({ posts: [] }), {
|
|
1530
|
+
status: 200,
|
|
1531
|
+
headers: { "Content-Type": "application/json" },
|
|
1532
|
+
}),
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
// Click the draft header button — should open drafts panel
|
|
1536
|
+
const draftBtn = requireElement(
|
|
1537
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
1538
|
+
"expected draft button",
|
|
1539
|
+
);
|
|
1540
|
+
draftBtn.click();
|
|
1541
|
+
await el.updateComplete;
|
|
1542
|
+
|
|
1543
|
+
expect(el._draftsPanelOpen).toBe(true);
|
|
1544
|
+
|
|
1545
|
+
// Wait for fetch to resolve
|
|
1546
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1547
|
+
await el.updateComplete;
|
|
1548
|
+
|
|
1549
|
+
expect(el._draftsLoading).toBe(false);
|
|
1550
|
+
expect(el.querySelector(".compose-drafts-panel")).not.toBeNull();
|
|
1551
|
+
|
|
1552
|
+
fetchSpy.mockRestore();
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
it("does not dispatch submit when loading", async () => {
|
|
1556
|
+
const el = await createElement();
|
|
1557
|
+
el._loading = true;
|
|
1558
|
+
await el.updateComplete;
|
|
1559
|
+
|
|
1560
|
+
let dispatched = false;
|
|
1561
|
+
el.addEventListener("jant:compose-submit-deferred", () => {
|
|
1562
|
+
dispatched = true;
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
const postBtn = requireElement(
|
|
1566
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1567
|
+
"expected post button",
|
|
1568
|
+
);
|
|
1569
|
+
postBtn.click();
|
|
1570
|
+
|
|
1571
|
+
expect(dispatched).toBe(false);
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
it("loading state shows spinner in submit button", async () => {
|
|
1575
|
+
const el = await createElement();
|
|
1576
|
+
el._loading = true;
|
|
1577
|
+
await el.updateComplete;
|
|
1578
|
+
|
|
1579
|
+
const spinner = el.querySelector(".compose-publish-main .animate-spin");
|
|
1580
|
+
expect(spinner).not.toBeNull();
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
it("no old media picker dialog is rendered", async () => {
|
|
1584
|
+
const el = await createElement();
|
|
1585
|
+
|
|
1586
|
+
expect(el.querySelector("#compose-media-picker")).toBeNull();
|
|
1587
|
+
expect(el.querySelector(".compose-media-picker")).toBeNull();
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
it("editor renders attachments when present", async () => {
|
|
1591
|
+
const el = await createElement();
|
|
1592
|
+
const editor = requireElement(
|
|
1593
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1594
|
+
"expected compose editor",
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
// Simulate adding an attachment
|
|
1598
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
1599
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
1600
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1601
|
+
|
|
1602
|
+
editor._attachments = [
|
|
1603
|
+
{
|
|
1604
|
+
clientId: "test-id-1",
|
|
1605
|
+
file,
|
|
1606
|
+
previewUrl,
|
|
1607
|
+
status: "done",
|
|
1608
|
+
progress: null,
|
|
1609
|
+
mediaId: "media-1",
|
|
1610
|
+
alt: "",
|
|
1611
|
+
error: null,
|
|
1612
|
+
summary: null,
|
|
1613
|
+
chars: null,
|
|
1614
|
+
},
|
|
1615
|
+
];
|
|
1616
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
1617
|
+
await editor.updateComplete;
|
|
1618
|
+
|
|
1619
|
+
// Thumbnail strip should be visible
|
|
1620
|
+
expect(editor.querySelector(".compose-attachments")).not.toBeNull();
|
|
1621
|
+
expect(editor.querySelector(".compose-attachment-thumb")).not.toBeNull();
|
|
1622
|
+
// ALT button should be visible
|
|
1623
|
+
expect(editor.querySelector(".compose-attachment-alt")).not.toBeNull();
|
|
1624
|
+
// Media tool button should show inline "Add" label
|
|
1625
|
+
const mediaBtn =
|
|
1626
|
+
editor.querySelector<HTMLButtonElement>(".compose-tool-btn");
|
|
1627
|
+
expect(mediaBtn?.querySelector(".compose-tool-label")?.textContent).toBe(
|
|
1628
|
+
"Add",
|
|
1629
|
+
);
|
|
1630
|
+
|
|
1631
|
+
URL.revokeObjectURL(previewUrl);
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
it("remove button clears attachment", async () => {
|
|
1635
|
+
const el = await createElement();
|
|
1636
|
+
const editor = requireElement(
|
|
1637
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1638
|
+
"expected compose editor",
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
1642
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
1643
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1644
|
+
|
|
1645
|
+
editor._attachments = [
|
|
1646
|
+
{
|
|
1647
|
+
clientId: "test-id-1",
|
|
1648
|
+
file,
|
|
1649
|
+
previewUrl,
|
|
1650
|
+
status: "done",
|
|
1651
|
+
progress: null,
|
|
1652
|
+
mediaId: "media-1",
|
|
1653
|
+
alt: "",
|
|
1654
|
+
error: null,
|
|
1655
|
+
summary: null,
|
|
1656
|
+
chars: null,
|
|
1657
|
+
},
|
|
1658
|
+
];
|
|
1659
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
1660
|
+
await editor.updateComplete;
|
|
1661
|
+
|
|
1662
|
+
// Click remove button
|
|
1663
|
+
const removeBtn = requireElement(
|
|
1664
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-remove"),
|
|
1665
|
+
"expected remove button",
|
|
1666
|
+
);
|
|
1667
|
+
removeBtn.click();
|
|
1668
|
+
await editor.updateComplete;
|
|
1669
|
+
|
|
1670
|
+
// Attachment strip should be gone (no attachments)
|
|
1671
|
+
expect(editor.querySelector(".compose-attachments")).toBeNull();
|
|
1672
|
+
expect(editor._attachments.length).toBe(0);
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
it("alt panel opens and closes", async () => {
|
|
1676
|
+
const el = await createElement();
|
|
1677
|
+
const editor = requireElement(
|
|
1678
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1679
|
+
"expected compose editor",
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
1683
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
1684
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1685
|
+
|
|
1686
|
+
editor._attachments = [
|
|
1687
|
+
{
|
|
1688
|
+
clientId: "test-id-1",
|
|
1689
|
+
file,
|
|
1690
|
+
previewUrl,
|
|
1691
|
+
status: "done",
|
|
1692
|
+
progress: null,
|
|
1693
|
+
mediaId: "media-1",
|
|
1694
|
+
alt: "",
|
|
1695
|
+
error: null,
|
|
1696
|
+
summary: null,
|
|
1697
|
+
chars: null,
|
|
1698
|
+
},
|
|
1699
|
+
];
|
|
1700
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
1701
|
+
await editor.updateComplete;
|
|
1702
|
+
|
|
1703
|
+
// Click ALT button
|
|
1704
|
+
const altBtn = requireElement(
|
|
1705
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-alt"),
|
|
1706
|
+
"expected alt button",
|
|
1707
|
+
);
|
|
1708
|
+
altBtn.click();
|
|
1709
|
+
await editor.updateComplete;
|
|
1710
|
+
await el.updateComplete;
|
|
1711
|
+
|
|
1712
|
+
// Alt panel should be visible in the dialog (covers entire dialog)
|
|
1713
|
+
expect(el.querySelector(".compose-alt-panel")).not.toBeNull();
|
|
1714
|
+
expect(editor._showAltPanel).toBe(true);
|
|
1715
|
+
|
|
1716
|
+
// Click done to close
|
|
1717
|
+
const doneBtn = el.querySelector<HTMLButtonElement>(
|
|
1718
|
+
".compose-alt-panel .compose-post-btn",
|
|
1719
|
+
);
|
|
1720
|
+
doneBtn?.click();
|
|
1721
|
+
await el.updateComplete;
|
|
1722
|
+
|
|
1723
|
+
expect(editor._showAltPanel).toBe(true); // Editor still tracks its own state
|
|
1724
|
+
expect(el.querySelector(".compose-alt-panel")).toBeNull();
|
|
1725
|
+
|
|
1726
|
+
URL.revokeObjectURL(previewUrl);
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
it("submit includes ordered attachment inputs from completed attachments", async () => {
|
|
1730
|
+
const el = await createElement();
|
|
1731
|
+
const editor = requireElement(
|
|
1732
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1733
|
+
"expected compose editor",
|
|
1734
|
+
);
|
|
1735
|
+
|
|
1736
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
1737
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
1738
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1739
|
+
|
|
1740
|
+
editor._attachments = [
|
|
1741
|
+
{
|
|
1742
|
+
clientId: "test-id-1",
|
|
1743
|
+
file,
|
|
1744
|
+
previewUrl,
|
|
1745
|
+
status: "done",
|
|
1746
|
+
progress: null,
|
|
1747
|
+
mediaId: "media-1",
|
|
1748
|
+
alt: "A test image",
|
|
1749
|
+
error: null,
|
|
1750
|
+
summary: null,
|
|
1751
|
+
chars: null,
|
|
1752
|
+
},
|
|
1753
|
+
];
|
|
1754
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
1755
|
+
editor._bodyJson = {
|
|
1756
|
+
type: "doc",
|
|
1757
|
+
content: [
|
|
1758
|
+
{
|
|
1759
|
+
type: "paragraph",
|
|
1760
|
+
content: [{ type: "text", text: "Post with image" }],
|
|
1761
|
+
},
|
|
1762
|
+
],
|
|
1763
|
+
};
|
|
1764
|
+
await editor.updateComplete;
|
|
1765
|
+
|
|
1766
|
+
let receivedDetail:
|
|
1767
|
+
| (ComposeSubmitDetail & { pendingAttachments: unknown[] })
|
|
1768
|
+
| null = null;
|
|
1769
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
1770
|
+
const customEvent = event as CustomEvent<
|
|
1771
|
+
ComposeSubmitDetail & { pendingAttachments: unknown[] }
|
|
1772
|
+
>;
|
|
1773
|
+
receivedDetail = customEvent.detail;
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
requireElement(
|
|
1777
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1778
|
+
"expected post button",
|
|
1779
|
+
).click();
|
|
1780
|
+
|
|
1781
|
+
expect(receivedDetail).not.toBeNull();
|
|
1782
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail & {
|
|
1783
|
+
pendingAttachments: unknown[];
|
|
1784
|
+
};
|
|
1785
|
+
expect(detail.attachments).toEqual([
|
|
1786
|
+
{
|
|
1787
|
+
type: "media",
|
|
1788
|
+
clientId: "test-id-1",
|
|
1789
|
+
mediaId: "media-1",
|
|
1790
|
+
alt: "A test image",
|
|
1791
|
+
},
|
|
1792
|
+
]);
|
|
1793
|
+
expect(detail.pendingAttachments).toEqual([]);
|
|
1794
|
+
|
|
1795
|
+
URL.revokeObjectURL(previewUrl);
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
it("dispatches deferred submit when uploads are pending", async () => {
|
|
390
1799
|
const el = await createElement();
|
|
391
1800
|
const editor = requireElement(
|
|
392
1801
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
393
1802
|
"expected compose editor",
|
|
394
1803
|
);
|
|
395
1804
|
|
|
396
|
-
// Simulate adding an attachment
|
|
397
1805
|
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
398
1806
|
const file = new File([blob], "test.png", { type: "image/png" });
|
|
399
1807
|
const previewUrl = URL.createObjectURL(blob);
|
|
400
1808
|
|
|
401
|
-
editor._attachments = [
|
|
402
|
-
{
|
|
403
|
-
clientId: "test-id-1",
|
|
404
|
-
file,
|
|
405
|
-
previewUrl,
|
|
406
|
-
status: "
|
|
407
|
-
progress: null,
|
|
408
|
-
mediaId:
|
|
409
|
-
alt: "",
|
|
410
|
-
error: null,
|
|
411
|
-
summary: null,
|
|
412
|
-
chars: null,
|
|
413
|
-
},
|
|
414
|
-
];
|
|
415
|
-
editor.
|
|
1809
|
+
editor._attachments = [
|
|
1810
|
+
{
|
|
1811
|
+
clientId: "test-id-1",
|
|
1812
|
+
file,
|
|
1813
|
+
previewUrl,
|
|
1814
|
+
status: "uploading",
|
|
1815
|
+
progress: null,
|
|
1816
|
+
mediaId: null,
|
|
1817
|
+
alt: "Alt for pending",
|
|
1818
|
+
error: null,
|
|
1819
|
+
summary: null,
|
|
1820
|
+
chars: null,
|
|
1821
|
+
},
|
|
1822
|
+
];
|
|
1823
|
+
editor._bodyJson = {
|
|
1824
|
+
type: "doc",
|
|
1825
|
+
content: [
|
|
1826
|
+
{
|
|
1827
|
+
type: "paragraph",
|
|
1828
|
+
content: [{ type: "text", text: "Post with pending upload" }],
|
|
1829
|
+
},
|
|
1830
|
+
],
|
|
1831
|
+
};
|
|
1832
|
+
await editor.updateComplete;
|
|
1833
|
+
|
|
1834
|
+
let deferredEvent: CustomEvent | null = null;
|
|
1835
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
1836
|
+
deferredEvent = event as CustomEvent;
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
requireElement(
|
|
1840
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
1841
|
+
"expected post button",
|
|
1842
|
+
).click();
|
|
1843
|
+
|
|
1844
|
+
expect(deferredEvent).not.toBeNull();
|
|
1845
|
+
expect(
|
|
1846
|
+
(deferredEvent as unknown as CustomEvent).detail.pendingAttachments,
|
|
1847
|
+
).toHaveLength(1);
|
|
1848
|
+
|
|
1849
|
+
URL.revokeObjectURL(previewUrl);
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
// ── Close confirmation ─────────────────────────────────────────────
|
|
1853
|
+
|
|
1854
|
+
it("requestClose on empty form closes immediately without confirmation", async () => {
|
|
1855
|
+
const el = await createElement();
|
|
1856
|
+
|
|
1857
|
+
// Ensure no confirmation panel appears
|
|
1858
|
+
el.requestClose();
|
|
1859
|
+
await el.updateComplete;
|
|
1860
|
+
|
|
1861
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
1862
|
+
expect(el.querySelector(".compose-confirm-panel")).toBeNull();
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
it("requestClose on empty form clears opener focus after closing", async () => {
|
|
1866
|
+
const el = await createElement();
|
|
1867
|
+
const trigger = document.createElement("button");
|
|
1868
|
+
trigger.type = "button";
|
|
1869
|
+
document.body.appendChild(trigger);
|
|
1870
|
+
|
|
1871
|
+
const dialog = document.createElement("dialog");
|
|
1872
|
+
const closeSpy = vi.spyOn(dialog, "close");
|
|
1873
|
+
const closestSpy = vi
|
|
1874
|
+
.spyOn(el, "closest")
|
|
1875
|
+
.mockImplementation((selector: string) =>
|
|
1876
|
+
selector === "dialog"
|
|
1877
|
+
? dialog
|
|
1878
|
+
: HTMLElement.prototype.closest.call(el, selector),
|
|
1879
|
+
);
|
|
1880
|
+
|
|
1881
|
+
trigger.focus();
|
|
1882
|
+
const blurSpy = vi.spyOn(trigger, "blur");
|
|
1883
|
+
|
|
1884
|
+
el.requestClose();
|
|
1885
|
+
await el.updateComplete;
|
|
1886
|
+
|
|
1887
|
+
expect(closeSpy).toHaveBeenCalledTimes(1);
|
|
1888
|
+
expect(blurSpy).toHaveBeenCalledTimes(1);
|
|
1889
|
+
expect(document.activeElement).not.toBe(trigger);
|
|
1890
|
+
|
|
1891
|
+
closestSpy.mockRestore();
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
it("treats dialog backdrop clicks as close requests", async () => {
|
|
1895
|
+
const el = await createElement();
|
|
1896
|
+
const dialog = document.createElement("dialog");
|
|
1897
|
+
const requestCloseSpy = vi.spyOn(el, "requestClose");
|
|
1898
|
+
(
|
|
1899
|
+
el as unknown as {
|
|
1900
|
+
_dialogEl: HTMLDialogElement | null;
|
|
1901
|
+
_handleDialogClick: (event: Event) => void;
|
|
1902
|
+
}
|
|
1903
|
+
)._dialogEl = dialog;
|
|
1904
|
+
vi.spyOn(document, "elementFromPoint").mockReturnValue(dialog);
|
|
1905
|
+
|
|
1906
|
+
(
|
|
1907
|
+
el as unknown as {
|
|
1908
|
+
_handleDialogClick: (event: Event) => void;
|
|
1909
|
+
}
|
|
1910
|
+
)._handleDialogClick({
|
|
1911
|
+
target: dialog,
|
|
1912
|
+
clientX: 24,
|
|
1913
|
+
clientY: 24,
|
|
1914
|
+
} as unknown as Event);
|
|
1915
|
+
|
|
1916
|
+
expect(requestCloseSpy).toHaveBeenCalledTimes(1);
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
it("ignores backdrop clicks that actually land on editor floating UI", async () => {
|
|
1920
|
+
const el = await createElement();
|
|
1921
|
+
const dialog = document.createElement("dialog");
|
|
1922
|
+
const requestCloseSpy = vi.spyOn(el, "requestClose");
|
|
1923
|
+
const floatingUi = document.createElement("div");
|
|
1924
|
+
floatingUi.setAttribute("data-editor-floating-ui", "true");
|
|
1925
|
+
(
|
|
1926
|
+
el as unknown as {
|
|
1927
|
+
_dialogEl: HTMLDialogElement | null;
|
|
1928
|
+
_handleDialogClick: (event: Event) => void;
|
|
1929
|
+
}
|
|
1930
|
+
)._dialogEl = dialog;
|
|
1931
|
+
vi.spyOn(document, "elementFromPoint").mockReturnValue(floatingUi);
|
|
1932
|
+
|
|
1933
|
+
(
|
|
1934
|
+
el as unknown as {
|
|
1935
|
+
_handleDialogClick: (event: Event) => void;
|
|
1936
|
+
}
|
|
1937
|
+
)._handleDialogClick({
|
|
1938
|
+
target: dialog,
|
|
1939
|
+
clientX: 24,
|
|
1940
|
+
clientY: 24,
|
|
1941
|
+
} as unknown as Event);
|
|
1942
|
+
|
|
1943
|
+
expect(requestCloseSpy).not.toHaveBeenCalled();
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
it("beforeunload does not warn when dialog was only opened", async () => {
|
|
1947
|
+
const el = await createElement();
|
|
1948
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
1949
|
+
open: true,
|
|
1950
|
+
addEventListener: vi.fn(),
|
|
1951
|
+
removeEventListener: vi.fn(),
|
|
1952
|
+
} as unknown as HTMLDialogElement);
|
|
1953
|
+
|
|
1954
|
+
const event = new Event("beforeunload", {
|
|
1955
|
+
cancelable: true,
|
|
1956
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
1957
|
+
|
|
1958
|
+
window.dispatchEvent(event);
|
|
1959
|
+
|
|
1960
|
+
expect(event.defaultPrevented).toBe(false);
|
|
1961
|
+
expect(
|
|
1962
|
+
(
|
|
1963
|
+
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
1964
|
+
)._hasUnsavedChanges(),
|
|
1965
|
+
).toBe(false);
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
it("beforeunload does not warn after switching to link without entering content", async () => {
|
|
1969
|
+
const el = await createElement();
|
|
1970
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
1971
|
+
open: true,
|
|
1972
|
+
addEventListener: vi.fn(),
|
|
1973
|
+
removeEventListener: vi.fn(),
|
|
1974
|
+
} as unknown as HTMLDialogElement);
|
|
1975
|
+
|
|
1976
|
+
el._format = "link";
|
|
1977
|
+
await flushUpdates(el);
|
|
1978
|
+
|
|
1979
|
+
const event = new Event("beforeunload", {
|
|
1980
|
+
cancelable: true,
|
|
1981
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
1982
|
+
|
|
1983
|
+
window.dispatchEvent(event);
|
|
1984
|
+
|
|
1985
|
+
expect(event.defaultPrevented).toBe(false);
|
|
1986
|
+
expect(
|
|
1987
|
+
(
|
|
1988
|
+
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
1989
|
+
)._hasUnsavedChanges(),
|
|
1990
|
+
).toBe(false);
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
it("beforeunload does not warn after switching to quote without entering content", async () => {
|
|
1994
|
+
const el = await createElement();
|
|
1995
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
1996
|
+
open: true,
|
|
1997
|
+
addEventListener: vi.fn(),
|
|
1998
|
+
removeEventListener: vi.fn(),
|
|
1999
|
+
} as unknown as HTMLDialogElement);
|
|
2000
|
+
|
|
2001
|
+
el._format = "quote";
|
|
2002
|
+
await flushUpdates(el);
|
|
2003
|
+
|
|
2004
|
+
const event = new Event("beforeunload", {
|
|
2005
|
+
cancelable: true,
|
|
2006
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
2007
|
+
|
|
2008
|
+
window.dispatchEvent(event);
|
|
2009
|
+
|
|
2010
|
+
expect(event.defaultPrevented).toBe(false);
|
|
2011
|
+
expect(
|
|
2012
|
+
(
|
|
2013
|
+
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
2014
|
+
)._hasUnsavedChanges(),
|
|
2015
|
+
).toBe(false);
|
|
2016
|
+
});
|
|
2017
|
+
|
|
2018
|
+
it("ignores empty attached text placeholders when checking unsaved changes", async () => {
|
|
2019
|
+
const el = await createElement();
|
|
2020
|
+
const editor = requireElement(
|
|
2021
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2022
|
+
"expected compose editor",
|
|
2023
|
+
);
|
|
2024
|
+
|
|
2025
|
+
editor._attachedTexts = [
|
|
2026
|
+
{
|
|
2027
|
+
clientId: "t1",
|
|
2028
|
+
bodyJson: null,
|
|
2029
|
+
bodyHtml: "",
|
|
2030
|
+
summary: "",
|
|
2031
|
+
},
|
|
2032
|
+
];
|
|
2033
|
+
editor._attachmentOrder = ["t1"];
|
|
2034
|
+
await editor.updateComplete;
|
|
2035
|
+
|
|
2036
|
+
expect(
|
|
2037
|
+
(
|
|
2038
|
+
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
2039
|
+
)._hasUnsavedChanges(),
|
|
2040
|
+
).toBe(false);
|
|
2041
|
+
});
|
|
2042
|
+
|
|
2043
|
+
it("beforeunload warns after compose content changes", async () => {
|
|
2044
|
+
const el = await createElement();
|
|
2045
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
2046
|
+
open: true,
|
|
2047
|
+
addEventListener: vi.fn(),
|
|
2048
|
+
removeEventListener: vi.fn(),
|
|
2049
|
+
} as unknown as HTMLDialogElement);
|
|
2050
|
+
const editor = requireElement(
|
|
2051
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2052
|
+
"expected compose editor",
|
|
2053
|
+
);
|
|
2054
|
+
|
|
2055
|
+
editor._bodyJson = {
|
|
2056
|
+
type: "doc",
|
|
2057
|
+
content: [
|
|
2058
|
+
{ type: "paragraph", content: [{ type: "text", text: "Unsaved" }] },
|
|
2059
|
+
],
|
|
2060
|
+
};
|
|
416
2061
|
await editor.updateComplete;
|
|
417
2062
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
2063
|
+
const event = new Event("beforeunload", {
|
|
2064
|
+
cancelable: true,
|
|
2065
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
2066
|
+
|
|
2067
|
+
window.dispatchEvent(event);
|
|
2068
|
+
|
|
2069
|
+
expect(event.defaultPrevented).toBe(true);
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2072
|
+
it("ignores native dialog cancel right after file picker cancel", async () => {
|
|
2073
|
+
const el = await createElement();
|
|
2074
|
+
const editor = requireElement(
|
|
2075
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2076
|
+
"expected compose editor",
|
|
428
2077
|
);
|
|
429
2078
|
|
|
430
|
-
|
|
2079
|
+
editor.dispatchEvent(
|
|
2080
|
+
new CustomEvent("jant:file-picker-open", {
|
|
2081
|
+
bubbles: true,
|
|
2082
|
+
composed: true,
|
|
2083
|
+
}),
|
|
2084
|
+
);
|
|
2085
|
+
editor.dispatchEvent(
|
|
2086
|
+
new CustomEvent("jant:file-picker-close", {
|
|
2087
|
+
bubbles: true,
|
|
2088
|
+
composed: true,
|
|
2089
|
+
detail: { cancelled: true },
|
|
2090
|
+
}),
|
|
2091
|
+
);
|
|
2092
|
+
|
|
2093
|
+
const requestCloseSpy = vi.spyOn(el, "requestClose");
|
|
2094
|
+
const cancelEvent = new Event("cancel", {
|
|
2095
|
+
cancelable: true,
|
|
2096
|
+
});
|
|
2097
|
+
|
|
2098
|
+
(
|
|
2099
|
+
el as unknown as {
|
|
2100
|
+
_handleDialogCancel: (event: Event) => void;
|
|
2101
|
+
}
|
|
2102
|
+
)._handleDialogCancel(cancelEvent);
|
|
2103
|
+
|
|
2104
|
+
expect(cancelEvent.defaultPrevented).toBe(true);
|
|
2105
|
+
expect(requestCloseSpy).not.toHaveBeenCalled();
|
|
431
2106
|
});
|
|
432
2107
|
|
|
433
|
-
it("
|
|
2108
|
+
it("ignores Escape right after file picker cancel", async () => {
|
|
434
2109
|
const el = await createElement();
|
|
435
2110
|
const editor = requireElement(
|
|
436
2111
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
437
2112
|
"expected compose editor",
|
|
438
2113
|
);
|
|
2114
|
+
editor._bodyJson = {
|
|
2115
|
+
type: "doc",
|
|
2116
|
+
content: [
|
|
2117
|
+
{
|
|
2118
|
+
type: "paragraph",
|
|
2119
|
+
content: [{ type: "text", text: "Keep open" }],
|
|
2120
|
+
},
|
|
2121
|
+
],
|
|
2122
|
+
};
|
|
2123
|
+
await editor.updateComplete;
|
|
2124
|
+
|
|
2125
|
+
editor.dispatchEvent(
|
|
2126
|
+
new CustomEvent("jant:file-picker-open", {
|
|
2127
|
+
bubbles: true,
|
|
2128
|
+
composed: true,
|
|
2129
|
+
}),
|
|
2130
|
+
);
|
|
2131
|
+
editor.dispatchEvent(
|
|
2132
|
+
new CustomEvent("jant:file-picker-close", {
|
|
2133
|
+
bubbles: true,
|
|
2134
|
+
composed: true,
|
|
2135
|
+
detail: { cancelled: true },
|
|
2136
|
+
}),
|
|
2137
|
+
);
|
|
439
2138
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
2139
|
+
el.dispatchEvent(
|
|
2140
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2141
|
+
key: "Escape",
|
|
2142
|
+
bubbles: true,
|
|
2143
|
+
}),
|
|
2144
|
+
);
|
|
2145
|
+
await el.updateComplete;
|
|
443
2146
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
2147
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
it("still closes normally after file picker selection", async () => {
|
|
2151
|
+
const el = await createElement();
|
|
2152
|
+
const editor = requireElement(
|
|
2153
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2154
|
+
"expected compose editor",
|
|
2155
|
+
);
|
|
2156
|
+
editor._bodyJson = {
|
|
2157
|
+
type: "doc",
|
|
2158
|
+
content: [
|
|
2159
|
+
{
|
|
2160
|
+
type: "paragraph",
|
|
2161
|
+
content: [{ type: "text", text: "Close after selecting" }],
|
|
2162
|
+
},
|
|
2163
|
+
],
|
|
2164
|
+
};
|
|
459
2165
|
await editor.updateComplete;
|
|
460
2166
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
2167
|
+
editor.dispatchEvent(
|
|
2168
|
+
new CustomEvent("jant:file-picker-open", {
|
|
2169
|
+
bubbles: true,
|
|
2170
|
+
composed: true,
|
|
2171
|
+
}),
|
|
465
2172
|
);
|
|
466
|
-
|
|
2173
|
+
editor.dispatchEvent(
|
|
2174
|
+
new CustomEvent("jant:file-picker-close", {
|
|
2175
|
+
bubbles: true,
|
|
2176
|
+
composed: true,
|
|
2177
|
+
detail: { cancelled: false },
|
|
2178
|
+
}),
|
|
2179
|
+
);
|
|
2180
|
+
|
|
2181
|
+
el.dispatchEvent(
|
|
2182
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2183
|
+
key: "Escape",
|
|
2184
|
+
bubbles: true,
|
|
2185
|
+
}),
|
|
2186
|
+
);
|
|
2187
|
+
await el.updateComplete;
|
|
2188
|
+
|
|
2189
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
it("clears file picker Escape suppression after pointer interaction", async () => {
|
|
2193
|
+
const el = await createElement();
|
|
2194
|
+
const editor = requireElement(
|
|
2195
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2196
|
+
"expected compose editor",
|
|
2197
|
+
);
|
|
2198
|
+
editor._bodyJson = {
|
|
2199
|
+
type: "doc",
|
|
2200
|
+
content: [
|
|
2201
|
+
{
|
|
2202
|
+
type: "paragraph",
|
|
2203
|
+
content: [{ type: "text", text: "Close after clicking back" }],
|
|
2204
|
+
},
|
|
2205
|
+
],
|
|
2206
|
+
};
|
|
467
2207
|
await editor.updateComplete;
|
|
468
2208
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
2209
|
+
editor.dispatchEvent(
|
|
2210
|
+
new CustomEvent("jant:file-picker-open", {
|
|
2211
|
+
bubbles: true,
|
|
2212
|
+
composed: true,
|
|
2213
|
+
}),
|
|
2214
|
+
);
|
|
2215
|
+
editor.dispatchEvent(
|
|
2216
|
+
new CustomEvent("jant:file-picker-close", {
|
|
2217
|
+
bubbles: true,
|
|
2218
|
+
composed: true,
|
|
2219
|
+
detail: { cancelled: true },
|
|
2220
|
+
}),
|
|
2221
|
+
);
|
|
2222
|
+
|
|
2223
|
+
el.dispatchEvent(
|
|
2224
|
+
new globalThis.PointerEvent("pointerdown", {
|
|
2225
|
+
bubbles: true,
|
|
2226
|
+
}),
|
|
2227
|
+
);
|
|
2228
|
+
el.dispatchEvent(
|
|
2229
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2230
|
+
key: "Escape",
|
|
2231
|
+
bubbles: true,
|
|
2232
|
+
}),
|
|
2233
|
+
);
|
|
2234
|
+
await el.updateComplete;
|
|
2235
|
+
|
|
2236
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
472
2237
|
});
|
|
473
2238
|
|
|
474
|
-
it("
|
|
2239
|
+
it("clears file picker Escape suppression after non-Escape key input", async () => {
|
|
475
2240
|
const el = await createElement();
|
|
476
2241
|
const editor = requireElement(
|
|
477
2242
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
478
2243
|
"expected compose editor",
|
|
479
2244
|
);
|
|
2245
|
+
editor._bodyJson = {
|
|
2246
|
+
type: "doc",
|
|
2247
|
+
content: [
|
|
2248
|
+
{
|
|
2249
|
+
type: "paragraph",
|
|
2250
|
+
content: [{ type: "text", text: "Close after typing again" }],
|
|
2251
|
+
},
|
|
2252
|
+
],
|
|
2253
|
+
};
|
|
2254
|
+
await editor.updateComplete;
|
|
480
2255
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
2256
|
+
editor.dispatchEvent(
|
|
2257
|
+
new CustomEvent("jant:file-picker-open", {
|
|
2258
|
+
bubbles: true,
|
|
2259
|
+
composed: true,
|
|
2260
|
+
}),
|
|
2261
|
+
);
|
|
2262
|
+
editor.dispatchEvent(
|
|
2263
|
+
new CustomEvent("jant:file-picker-close", {
|
|
2264
|
+
bubbles: true,
|
|
2265
|
+
composed: true,
|
|
2266
|
+
detail: { cancelled: true },
|
|
2267
|
+
}),
|
|
2268
|
+
);
|
|
484
2269
|
|
|
485
|
-
|
|
486
|
-
{
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
2270
|
+
el.dispatchEvent(
|
|
2271
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2272
|
+
key: "a",
|
|
2273
|
+
bubbles: true,
|
|
2274
|
+
}),
|
|
2275
|
+
);
|
|
2276
|
+
el.dispatchEvent(
|
|
2277
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2278
|
+
key: "Escape",
|
|
2279
|
+
bubbles: true,
|
|
2280
|
+
}),
|
|
2281
|
+
);
|
|
2282
|
+
await el.updateComplete;
|
|
2283
|
+
|
|
2284
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
2285
|
+
});
|
|
2286
|
+
|
|
2287
|
+
it("Escape cancels slash commands without opening the close confirmation", async () => {
|
|
2288
|
+
const el = await createElement();
|
|
2289
|
+
const editor = requireElement(
|
|
2290
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2291
|
+
"expected compose editor",
|
|
2292
|
+
);
|
|
2293
|
+
const tiptap = requireEditor(editor);
|
|
2294
|
+
|
|
2295
|
+
tiptap.commands.focus("end");
|
|
2296
|
+
tiptap.commands.insertContent("/");
|
|
2297
|
+
await flushUpdates(el);
|
|
2298
|
+
|
|
2299
|
+
expect(document.querySelector(".tiptap-slash-menu")).not.toBeNull();
|
|
2300
|
+
|
|
2301
|
+
const requestCloseSpy = vi.spyOn(el, "requestClose");
|
|
2302
|
+
const escapeEvent = new globalThis.KeyboardEvent("keydown", {
|
|
2303
|
+
key: "Escape",
|
|
2304
|
+
bubbles: true,
|
|
2305
|
+
cancelable: true,
|
|
2306
|
+
});
|
|
2307
|
+
tiptap.view.dom.dispatchEvent(escapeEvent);
|
|
2308
|
+
await flushUpdates(el);
|
|
2309
|
+
|
|
2310
|
+
expect(escapeEvent.defaultPrevented).toBe(true);
|
|
2311
|
+
expect(requestCloseSpy).not.toHaveBeenCalled();
|
|
2312
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2313
|
+
expect(document.querySelector(".tiptap-slash-menu")).toBeNull();
|
|
2314
|
+
expect(tiptap.getText()).toBe("");
|
|
2315
|
+
});
|
|
2316
|
+
|
|
2317
|
+
it("shows a clear empty state when slash commands have no matches", async () => {
|
|
2318
|
+
const el = await createElement();
|
|
2319
|
+
const editor = requireElement(
|
|
2320
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2321
|
+
"expected compose editor",
|
|
2322
|
+
);
|
|
2323
|
+
const tiptap = requireEditor(editor);
|
|
2324
|
+
|
|
2325
|
+
tiptap.commands.focus("end");
|
|
2326
|
+
tiptap.commands.insertContent("/zzz");
|
|
2327
|
+
await flushUpdates(el);
|
|
2328
|
+
|
|
2329
|
+
const menu = requireElement(
|
|
2330
|
+
document.querySelector<HTMLElement>(".tiptap-slash-menu"),
|
|
2331
|
+
"expected slash menu",
|
|
2332
|
+
);
|
|
2333
|
+
expect(menu.querySelectorAll(".tiptap-slash-item")).toHaveLength(0);
|
|
2334
|
+
expect(
|
|
2335
|
+
requireElement(
|
|
2336
|
+
menu.querySelector<HTMLElement>(".tiptap-slash-empty"),
|
|
2337
|
+
"expected slash empty state",
|
|
2338
|
+
).textContent,
|
|
2339
|
+
).toContain("No matches. Try another command.");
|
|
2340
|
+
|
|
2341
|
+
const requestCloseSpy = vi.spyOn(el, "requestClose");
|
|
2342
|
+
const enterEvent = new globalThis.KeyboardEvent("keydown", {
|
|
2343
|
+
key: "Enter",
|
|
2344
|
+
bubbles: true,
|
|
2345
|
+
cancelable: true,
|
|
2346
|
+
});
|
|
2347
|
+
tiptap.view.dom.dispatchEvent(enterEvent);
|
|
2348
|
+
await flushUpdates(el);
|
|
2349
|
+
|
|
2350
|
+
expect(enterEvent.defaultPrevented).toBe(true);
|
|
2351
|
+
expect(requestCloseSpy).not.toHaveBeenCalled();
|
|
2352
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2353
|
+
expect(tiptap.getText()).toBe("/zzz");
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
it("dialog cancel closes the emoji picker before prompting to save", async () => {
|
|
2357
|
+
const el = await createElement();
|
|
2358
|
+
const editor = requireElement(
|
|
2359
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2360
|
+
"expected compose editor",
|
|
2361
|
+
);
|
|
2362
|
+
editor._bodyJson = {
|
|
2363
|
+
type: "doc",
|
|
2364
|
+
content: [
|
|
2365
|
+
{ type: "paragraph", content: [{ type: "text", text: "Keep typing" }] },
|
|
2366
|
+
],
|
|
2367
|
+
};
|
|
2368
|
+
editor._showEmojiPicker = true;
|
|
500
2369
|
await editor.updateComplete;
|
|
501
2370
|
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
2371
|
+
const closeEmojiPickerSpy = vi.spyOn(editor, "closeEmojiPicker");
|
|
2372
|
+
const focusSpy = vi.spyOn(editor, "focusInput");
|
|
2373
|
+
|
|
2374
|
+
(
|
|
2375
|
+
el as unknown as {
|
|
2376
|
+
_handleDialogCancel: (event: Event) => void;
|
|
2377
|
+
}
|
|
2378
|
+
)._handleDialogCancel(new Event("cancel", { cancelable: true }));
|
|
2379
|
+
await flushUpdates(el);
|
|
2380
|
+
|
|
2381
|
+
expect(closeEmojiPickerSpy).toHaveBeenCalledWith({ restoreFocus: true });
|
|
2382
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2383
|
+
expect(focusSpy).toHaveBeenCalledWith("end");
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
it("dialog cancel closes the collection selector and keeps selected collections", async () => {
|
|
2387
|
+
const el = await createElement();
|
|
2388
|
+
const editor = requireElement(
|
|
2389
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2390
|
+
"expected compose editor",
|
|
506
2391
|
);
|
|
507
|
-
|
|
2392
|
+
editor._bodyJson = {
|
|
2393
|
+
type: "doc",
|
|
2394
|
+
content: [
|
|
2395
|
+
{ type: "paragraph", content: [{ type: "text", text: "Tagged post" }] },
|
|
2396
|
+
],
|
|
2397
|
+
};
|
|
2398
|
+
el._showCollection = true;
|
|
2399
|
+
el._collectionIds = ["col-1"];
|
|
508
2400
|
await editor.updateComplete;
|
|
509
|
-
await el.updateComplete;
|
|
510
|
-
|
|
511
|
-
// Alt panel should be visible in the dialog (covers entire dialog)
|
|
512
|
-
expect(el.querySelector(".compose-alt-panel")).not.toBeNull();
|
|
513
|
-
expect(editor._showAltPanel).toBe(true);
|
|
514
2401
|
|
|
515
|
-
|
|
516
|
-
const doneBtn = el.querySelector<HTMLButtonElement>(
|
|
517
|
-
".compose-alt-panel .compose-post-btn",
|
|
518
|
-
);
|
|
519
|
-
doneBtn?.click();
|
|
520
|
-
await el.updateComplete;
|
|
2402
|
+
const focusSpy = vi.spyOn(editor, "focusInput");
|
|
521
2403
|
|
|
522
|
-
|
|
523
|
-
|
|
2404
|
+
(
|
|
2405
|
+
el as unknown as {
|
|
2406
|
+
_handleDialogCancel: (event: Event) => void;
|
|
2407
|
+
}
|
|
2408
|
+
)._handleDialogCancel(new Event("cancel", { cancelable: true }));
|
|
2409
|
+
await flushUpdates(el);
|
|
524
2410
|
|
|
525
|
-
|
|
2411
|
+
expect(el._showCollection).toBe(false);
|
|
2412
|
+
expect(el._collectionIds).toEqual(["col-1"]);
|
|
2413
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2414
|
+
expect(focusSpy).toHaveBeenCalledWith("end");
|
|
526
2415
|
});
|
|
527
2416
|
|
|
528
|
-
it("
|
|
2417
|
+
it("dialog cancel closes publish settings and returns focus to the editor", async () => {
|
|
529
2418
|
const el = await createElement();
|
|
530
2419
|
const editor = requireElement(
|
|
531
2420
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
532
2421
|
"expected compose editor",
|
|
533
2422
|
);
|
|
534
|
-
|
|
535
|
-
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
536
|
-
const file = new File([blob], "test.png", { type: "image/png" });
|
|
537
|
-
const previewUrl = URL.createObjectURL(blob);
|
|
538
|
-
|
|
539
|
-
editor._attachments = [
|
|
540
|
-
{
|
|
541
|
-
clientId: "test-id-1",
|
|
542
|
-
file,
|
|
543
|
-
previewUrl,
|
|
544
|
-
status: "done",
|
|
545
|
-
progress: null,
|
|
546
|
-
mediaId: "media-1",
|
|
547
|
-
alt: "A test image",
|
|
548
|
-
error: null,
|
|
549
|
-
summary: null,
|
|
550
|
-
chars: null,
|
|
551
|
-
},
|
|
552
|
-
];
|
|
553
2423
|
editor._bodyJson = {
|
|
554
2424
|
type: "doc",
|
|
555
2425
|
content: [
|
|
556
2426
|
{
|
|
557
2427
|
type: "paragraph",
|
|
558
|
-
content: [{ type: "text", text: "
|
|
2428
|
+
content: [{ type: "text", text: "Publish panel content" }],
|
|
559
2429
|
},
|
|
560
2430
|
],
|
|
561
2431
|
};
|
|
2432
|
+
el._showPublishPanel = true;
|
|
562
2433
|
await editor.updateComplete;
|
|
563
2434
|
|
|
564
|
-
|
|
565
|
-
| (ComposeSubmitDetail & { pendingAttachments: unknown[] })
|
|
566
|
-
| null = null;
|
|
567
|
-
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
568
|
-
const customEvent = event as CustomEvent<
|
|
569
|
-
ComposeSubmitDetail & { pendingAttachments: unknown[] }
|
|
570
|
-
>;
|
|
571
|
-
receivedDetail = customEvent.detail;
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
requireElement(
|
|
575
|
-
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
576
|
-
"expected post button",
|
|
577
|
-
).click();
|
|
2435
|
+
const focusSpy = vi.spyOn(editor, "focusInput");
|
|
578
2436
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
expect(detail.pendingAttachments).toEqual([]);
|
|
2437
|
+
(
|
|
2438
|
+
el as unknown as {
|
|
2439
|
+
_handleDialogCancel: (event: Event) => void;
|
|
2440
|
+
}
|
|
2441
|
+
)._handleDialogCancel(new Event("cancel", { cancelable: true }));
|
|
2442
|
+
await flushUpdates(el);
|
|
586
2443
|
|
|
587
|
-
|
|
2444
|
+
expect(el._showPublishPanel).toBe(false);
|
|
2445
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2446
|
+
expect(focusSpy).toHaveBeenCalledWith("end");
|
|
588
2447
|
});
|
|
589
2448
|
|
|
590
|
-
it("
|
|
2449
|
+
it("requestClose with content shows confirmation panel", async () => {
|
|
591
2450
|
const el = await createElement();
|
|
592
2451
|
const editor = requireElement(
|
|
593
2452
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
594
2453
|
"expected compose editor",
|
|
595
2454
|
);
|
|
2455
|
+
editor._bodyJson = {
|
|
2456
|
+
type: "doc",
|
|
2457
|
+
content: [
|
|
2458
|
+
{ type: "paragraph", content: [{ type: "text", text: "Some text" }] },
|
|
2459
|
+
],
|
|
2460
|
+
};
|
|
2461
|
+
await editor.updateComplete;
|
|
596
2462
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const previewUrl = URL.createObjectURL(blob);
|
|
2463
|
+
el.requestClose();
|
|
2464
|
+
await el.updateComplete;
|
|
600
2465
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
];
|
|
2466
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
2467
|
+
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
2468
|
+
expect(
|
|
2469
|
+
el.querySelector(".compose-confirm-title")?.textContent?.trim(),
|
|
2470
|
+
).toBe("Save to drafts?");
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
it("Cmd/Ctrl+Enter publishes from the main compose editor", async () => {
|
|
2474
|
+
const el = await createElement();
|
|
2475
|
+
const editor = requireElement(
|
|
2476
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2477
|
+
"expected compose editor",
|
|
2478
|
+
);
|
|
615
2479
|
editor._bodyJson = {
|
|
616
2480
|
type: "doc",
|
|
617
2481
|
content: [
|
|
618
2482
|
{
|
|
619
2483
|
type: "paragraph",
|
|
620
|
-
content: [{ type: "text", text: "
|
|
2484
|
+
content: [{ type: "text", text: "Publish from shortcut" }],
|
|
621
2485
|
},
|
|
622
2486
|
],
|
|
623
2487
|
};
|
|
624
2488
|
await editor.updateComplete;
|
|
625
2489
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
requireElement(
|
|
632
|
-
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
633
|
-
"expected post button",
|
|
634
|
-
).click();
|
|
2490
|
+
const submitSpy = vi.spyOn(
|
|
2491
|
+
el as unknown as { _submit: (status: "published" | "draft") => void },
|
|
2492
|
+
"_submit",
|
|
2493
|
+
);
|
|
635
2494
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
2495
|
+
el.dispatchEvent(
|
|
2496
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2497
|
+
key: "Enter",
|
|
2498
|
+
metaKey: true,
|
|
2499
|
+
bubbles: true,
|
|
2500
|
+
}),
|
|
2501
|
+
);
|
|
640
2502
|
|
|
641
|
-
|
|
2503
|
+
expect(submitSpy).toHaveBeenCalledWith("published");
|
|
642
2504
|
});
|
|
643
2505
|
|
|
644
|
-
|
|
2506
|
+
it("Cmd/Ctrl+Enter focuses the link URL when the URL is invalid", async () => {
|
|
2507
|
+
const el = await createElement();
|
|
2508
|
+
el._format = "link";
|
|
2509
|
+
await el.updateComplete;
|
|
645
2510
|
|
|
646
|
-
|
|
2511
|
+
const editor = requireElement(
|
|
2512
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2513
|
+
"expected compose editor",
|
|
2514
|
+
);
|
|
2515
|
+
editor._url = "example.com";
|
|
2516
|
+
await editor.updateComplete;
|
|
2517
|
+
|
|
2518
|
+
const focusUrlSpy = vi.spyOn(editor, "focusUrlInput");
|
|
2519
|
+
const submitSpy = vi.spyOn(
|
|
2520
|
+
el as unknown as { _submit: (status: "published" | "draft") => void },
|
|
2521
|
+
"_submit",
|
|
2522
|
+
);
|
|
2523
|
+
|
|
2524
|
+
el.dispatchEvent(
|
|
2525
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2526
|
+
key: "Enter",
|
|
2527
|
+
metaKey: true,
|
|
2528
|
+
bubbles: true,
|
|
2529
|
+
}),
|
|
2530
|
+
);
|
|
2531
|
+
|
|
2532
|
+
expect(focusUrlSpy).toHaveBeenCalledWith("end");
|
|
2533
|
+
expect(submitSpy).not.toHaveBeenCalled();
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
it("blocks publish for link posts without showing a title error on blur", async () => {
|
|
647
2537
|
const el = await createElement();
|
|
2538
|
+
el._format = "link";
|
|
2539
|
+
await el.updateComplete;
|
|
648
2540
|
|
|
649
|
-
|
|
650
|
-
|
|
2541
|
+
const editor = requireElement(
|
|
2542
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
2543
|
+
"expected compose editor",
|
|
2544
|
+
);
|
|
2545
|
+
editor._url = "https://example.com";
|
|
2546
|
+
await editor.updateComplete;
|
|
2547
|
+
|
|
2548
|
+
expect(
|
|
2549
|
+
requireElement(
|
|
2550
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
2551
|
+
"expected publish button",
|
|
2552
|
+
).disabled,
|
|
2553
|
+
).toBe(true);
|
|
2554
|
+
|
|
2555
|
+
const titleInput = requireElement(
|
|
2556
|
+
el.querySelector<HTMLInputElement>(".compose-link-title"),
|
|
2557
|
+
"expected link title input",
|
|
2558
|
+
);
|
|
2559
|
+
titleInput.dispatchEvent(new Event("blur"));
|
|
651
2560
|
await el.updateComplete;
|
|
652
2561
|
|
|
653
|
-
expect(el.
|
|
654
|
-
expect(el.querySelector(".compose-confirm-panel")).toBeNull();
|
|
2562
|
+
expect(el.querySelector("[data-compose-link-title-error]")).toBeNull();
|
|
655
2563
|
});
|
|
656
2564
|
|
|
657
|
-
it("
|
|
2565
|
+
it("Cmd/Ctrl+Enter finishes an attached text editor instead of publishing", async () => {
|
|
658
2566
|
const el = await createElement();
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
2567
|
+
(
|
|
2568
|
+
el as unknown as {
|
|
2569
|
+
_attachedPanelOpen: boolean;
|
|
2570
|
+
}
|
|
2571
|
+
)._attachedPanelOpen = true;
|
|
2572
|
+
|
|
2573
|
+
const doneSpy = vi.spyOn(
|
|
2574
|
+
el as unknown as { _doneAttachedPanel: () => void },
|
|
2575
|
+
"_doneAttachedPanel",
|
|
2576
|
+
);
|
|
2577
|
+
const submitSpy = vi.spyOn(
|
|
2578
|
+
el as unknown as { _submit: (status: "published" | "draft") => void },
|
|
2579
|
+
"_submit",
|
|
2580
|
+
);
|
|
668
2581
|
|
|
669
|
-
|
|
2582
|
+
el.dispatchEvent(
|
|
2583
|
+
new globalThis.KeyboardEvent("keydown", {
|
|
2584
|
+
key: "Enter",
|
|
2585
|
+
metaKey: true,
|
|
2586
|
+
bubbles: true,
|
|
2587
|
+
}),
|
|
2588
|
+
);
|
|
670
2589
|
|
|
671
|
-
expect(
|
|
672
|
-
expect(
|
|
673
|
-
(
|
|
674
|
-
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
675
|
-
)._hasUnsavedChanges(),
|
|
676
|
-
).toBe(false);
|
|
2590
|
+
expect(doneSpy).toHaveBeenCalled();
|
|
2591
|
+
expect(submitSpy).not.toHaveBeenCalled();
|
|
677
2592
|
});
|
|
678
2593
|
|
|
679
|
-
it("
|
|
2594
|
+
it("cancelling an empty attached text editor discards it without confirmation", async () => {
|
|
680
2595
|
const el = await createElement();
|
|
681
|
-
vi.spyOn(el, "closest").mockReturnValue({
|
|
682
|
-
open: true,
|
|
683
|
-
addEventListener: vi.fn(),
|
|
684
|
-
removeEventListener: vi.fn(),
|
|
685
|
-
} as unknown as HTMLDialogElement);
|
|
686
2596
|
const editor = requireElement(
|
|
687
2597
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
688
2598
|
"expected compose editor",
|
|
689
2599
|
);
|
|
690
2600
|
|
|
691
|
-
editor.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
2601
|
+
editor._attachedTexts = [
|
|
2602
|
+
{
|
|
2603
|
+
clientId: "t1",
|
|
2604
|
+
bodyJson: null,
|
|
2605
|
+
bodyHtml: "",
|
|
2606
|
+
summary: "",
|
|
2607
|
+
},
|
|
2608
|
+
];
|
|
2609
|
+
editor._attachmentOrder = ["t1"];
|
|
697
2610
|
await editor.updateComplete;
|
|
698
2611
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
2612
|
+
(
|
|
2613
|
+
el as unknown as {
|
|
2614
|
+
_attachedTextIndex: number;
|
|
2615
|
+
_attachedPanelOpen: boolean;
|
|
2616
|
+
_attachedEditor: { getJSON(): unknown; destroy(): void } | null;
|
|
2617
|
+
_attachedTextSnapshot: unknown;
|
|
2618
|
+
_cancelAttachedPanel: () => Promise<void>;
|
|
2619
|
+
}
|
|
2620
|
+
)._attachedTextIndex = 0;
|
|
2621
|
+
(
|
|
2622
|
+
el as unknown as {
|
|
2623
|
+
_attachedPanelOpen: boolean;
|
|
2624
|
+
}
|
|
2625
|
+
)._attachedPanelOpen = true;
|
|
2626
|
+
(
|
|
2627
|
+
el as unknown as {
|
|
2628
|
+
_attachedEditor: { getJSON(): unknown; destroy(): void } | null;
|
|
2629
|
+
}
|
|
2630
|
+
)._attachedEditor = {
|
|
2631
|
+
getJSON: () => ({
|
|
2632
|
+
type: "doc",
|
|
2633
|
+
content: [{ type: "paragraph" }],
|
|
2634
|
+
}),
|
|
2635
|
+
destroy: vi.fn(),
|
|
2636
|
+
};
|
|
2637
|
+
(
|
|
2638
|
+
el as unknown as {
|
|
2639
|
+
_attachedTextSnapshot: unknown;
|
|
2640
|
+
}
|
|
2641
|
+
)._attachedTextSnapshot = null;
|
|
2642
|
+
|
|
2643
|
+
await (
|
|
2644
|
+
el as unknown as {
|
|
2645
|
+
_cancelAttachedPanel: () => Promise<void>;
|
|
2646
|
+
}
|
|
2647
|
+
)._cancelAttachedPanel();
|
|
2648
|
+
|
|
2649
|
+
expect(el._attachedPanelOpen).toBe(false);
|
|
2650
|
+
expect(editor._attachedTexts).toEqual([]);
|
|
2651
|
+
expect(editor._attachmentOrder).toEqual([]);
|
|
706
2652
|
});
|
|
707
2653
|
|
|
708
|
-
it("
|
|
2654
|
+
it("reopening an attached text editor places the cursor at the end", async () => {
|
|
709
2655
|
const el = await createElement();
|
|
710
2656
|
const editor = requireElement(
|
|
711
2657
|
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
712
2658
|
"expected compose editor",
|
|
713
2659
|
);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
2660
|
+
|
|
2661
|
+
editor._attachedTexts = [
|
|
2662
|
+
{
|
|
2663
|
+
clientId: "t1",
|
|
2664
|
+
bodyJson: {
|
|
2665
|
+
type: "doc",
|
|
2666
|
+
content: [
|
|
2667
|
+
{
|
|
2668
|
+
type: "paragraph",
|
|
2669
|
+
content: [{ type: "text", text: "Existing attachment body" }],
|
|
2670
|
+
},
|
|
2671
|
+
],
|
|
2672
|
+
},
|
|
2673
|
+
bodyHtml: "<p>Existing attachment body</p>",
|
|
2674
|
+
summary: "Existing attachment body",
|
|
2675
|
+
},
|
|
2676
|
+
];
|
|
2677
|
+
editor._attachmentOrder = ["t1"];
|
|
720
2678
|
await editor.updateComplete;
|
|
721
2679
|
|
|
722
|
-
|
|
2680
|
+
(
|
|
2681
|
+
el as unknown as {
|
|
2682
|
+
_handleAttachedPanelOpen: (event: Event) => void;
|
|
2683
|
+
}
|
|
2684
|
+
)._handleAttachedPanelOpen(
|
|
2685
|
+
new CustomEvent("jant:attached-panel-open", {
|
|
2686
|
+
detail: { index: 0 },
|
|
2687
|
+
}),
|
|
2688
|
+
);
|
|
2689
|
+
await flushUpdates(el);
|
|
2690
|
+
|
|
2691
|
+
const attachedEditor = (
|
|
2692
|
+
el as unknown as {
|
|
2693
|
+
_attachedEditor: {
|
|
2694
|
+
state: {
|
|
2695
|
+
selection: { from: number; to: number };
|
|
2696
|
+
};
|
|
2697
|
+
} | null;
|
|
2698
|
+
}
|
|
2699
|
+
)._attachedEditor;
|
|
2700
|
+
if (!attachedEditor) {
|
|
2701
|
+
throw new Error("expected attached editor instance");
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
expect(attachedEditor.state.selection.from).toBeGreaterThan(1);
|
|
2705
|
+
expect(attachedEditor.state.selection.from).toBe(
|
|
2706
|
+
attachedEditor.state.selection.to,
|
|
2707
|
+
);
|
|
2708
|
+
});
|
|
2709
|
+
|
|
2710
|
+
it("cancelling a dirty attached text editor uses the shared three-action confirm panel", async () => {
|
|
2711
|
+
const el = await createElement();
|
|
2712
|
+
|
|
2713
|
+
(
|
|
2714
|
+
el as unknown as {
|
|
2715
|
+
_attachedTextIndex: number;
|
|
2716
|
+
_attachedPanelOpen: boolean;
|
|
2717
|
+
_attachedEditor: {
|
|
2718
|
+
commands: { focus: () => void };
|
|
2719
|
+
destroy(): void;
|
|
2720
|
+
getJSON(): unknown;
|
|
2721
|
+
} | null;
|
|
2722
|
+
_attachedTextSnapshot: unknown;
|
|
2723
|
+
_cancelAttachedPanel: () => void;
|
|
2724
|
+
}
|
|
2725
|
+
)._attachedTextIndex = 0;
|
|
2726
|
+
(
|
|
2727
|
+
el as unknown as {
|
|
2728
|
+
_attachedPanelOpen: boolean;
|
|
2729
|
+
}
|
|
2730
|
+
)._attachedPanelOpen = true;
|
|
2731
|
+
(
|
|
2732
|
+
el as unknown as {
|
|
2733
|
+
_attachedEditor: {
|
|
2734
|
+
commands: { focus: () => void };
|
|
2735
|
+
destroy(): void;
|
|
2736
|
+
getJSON(): unknown;
|
|
2737
|
+
} | null;
|
|
2738
|
+
}
|
|
2739
|
+
)._attachedEditor = {
|
|
2740
|
+
commands: { focus: vi.fn() },
|
|
2741
|
+
destroy: vi.fn(),
|
|
2742
|
+
getJSON: () => ({
|
|
2743
|
+
type: "doc",
|
|
2744
|
+
content: [
|
|
2745
|
+
{
|
|
2746
|
+
type: "paragraph",
|
|
2747
|
+
content: [{ type: "text", text: "Changed attachment" }],
|
|
2748
|
+
},
|
|
2749
|
+
],
|
|
2750
|
+
}),
|
|
2751
|
+
};
|
|
2752
|
+
(
|
|
2753
|
+
el as unknown as {
|
|
2754
|
+
_attachedTextSnapshot: unknown;
|
|
2755
|
+
}
|
|
2756
|
+
)._attachedTextSnapshot = null;
|
|
2757
|
+
|
|
2758
|
+
(
|
|
2759
|
+
el as unknown as {
|
|
2760
|
+
_cancelAttachedPanel: () => void;
|
|
2761
|
+
}
|
|
2762
|
+
)._cancelAttachedPanel();
|
|
723
2763
|
await el.updateComplete;
|
|
724
2764
|
|
|
725
2765
|
expect(el._confirmPanelOpen).toBe(true);
|
|
726
2766
|
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
727
2767
|
expect(
|
|
728
2768
|
el.querySelector(".compose-confirm-title")?.textContent?.trim(),
|
|
729
|
-
).toBe("Save
|
|
2769
|
+
).toBe("Save text attachment?");
|
|
2770
|
+
expect(el.querySelector(".compose-confirm-save")?.textContent?.trim()).toBe(
|
|
2771
|
+
"Save",
|
|
2772
|
+
);
|
|
2773
|
+
expect(
|
|
2774
|
+
el.querySelector(".compose-confirm-discard")?.textContent?.trim(),
|
|
2775
|
+
).toBe("Don't save");
|
|
2776
|
+
expect(
|
|
2777
|
+
el.querySelector(".compose-confirm-cancel")?.textContent?.trim(),
|
|
2778
|
+
).toBe("Cancel");
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
it("cancel on attached text confirm returns focus to the attached editor", async () => {
|
|
2782
|
+
const el = await createElement();
|
|
2783
|
+
const focusSpy = vi.fn();
|
|
2784
|
+
|
|
2785
|
+
(
|
|
2786
|
+
el as unknown as {
|
|
2787
|
+
_attachedPanelOpen: boolean;
|
|
2788
|
+
_confirmPanelOpen: boolean;
|
|
2789
|
+
_confirmForAttachedText: boolean;
|
|
2790
|
+
_attachedEditor: {
|
|
2791
|
+
commands: { focus: () => void };
|
|
2792
|
+
destroy(): void;
|
|
2793
|
+
} | null;
|
|
2794
|
+
}
|
|
2795
|
+
)._attachedPanelOpen = true;
|
|
2796
|
+
(
|
|
2797
|
+
el as unknown as {
|
|
2798
|
+
_confirmPanelOpen: boolean;
|
|
2799
|
+
_confirmForAttachedText: boolean;
|
|
2800
|
+
_attachedEditor: {
|
|
2801
|
+
commands: { focus: () => void };
|
|
2802
|
+
destroy(): void;
|
|
2803
|
+
} | null;
|
|
2804
|
+
}
|
|
2805
|
+
)._confirmPanelOpen = true;
|
|
2806
|
+
(
|
|
2807
|
+
el as unknown as {
|
|
2808
|
+
_confirmForAttachedText: boolean;
|
|
2809
|
+
_attachedEditor: {
|
|
2810
|
+
commands: { focus: () => void };
|
|
2811
|
+
destroy(): void;
|
|
2812
|
+
} | null;
|
|
2813
|
+
}
|
|
2814
|
+
)._confirmForAttachedText = true;
|
|
2815
|
+
(
|
|
2816
|
+
el as unknown as {
|
|
2817
|
+
_attachedEditor: {
|
|
2818
|
+
commands: { focus: () => void };
|
|
2819
|
+
destroy(): void;
|
|
2820
|
+
} | null;
|
|
2821
|
+
}
|
|
2822
|
+
)._attachedEditor = {
|
|
2823
|
+
commands: { focus: focusSpy },
|
|
2824
|
+
destroy: vi.fn(),
|
|
2825
|
+
};
|
|
2826
|
+
|
|
2827
|
+
el.requestClose();
|
|
2828
|
+
await el.updateComplete;
|
|
2829
|
+
await flushUpdates(el);
|
|
2830
|
+
|
|
2831
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
2832
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2835
|
+
it("clicking the empty attached editor area focuses the attached editor", async () => {
|
|
2836
|
+
const el = await createElement();
|
|
2837
|
+
const focusSpy = vi.fn();
|
|
2838
|
+
|
|
2839
|
+
(
|
|
2840
|
+
el as unknown as {
|
|
2841
|
+
_attachedPanelOpen: boolean;
|
|
2842
|
+
_attachedEditor: {
|
|
2843
|
+
commands: { focus: () => void };
|
|
2844
|
+
destroy(): void;
|
|
2845
|
+
getJSON(): unknown;
|
|
2846
|
+
} | null;
|
|
2847
|
+
}
|
|
2848
|
+
)._attachedPanelOpen = true;
|
|
2849
|
+
(
|
|
2850
|
+
el as unknown as {
|
|
2851
|
+
_attachedEditor: {
|
|
2852
|
+
commands: { focus: () => void };
|
|
2853
|
+
destroy(): void;
|
|
2854
|
+
getJSON(): unknown;
|
|
2855
|
+
} | null;
|
|
2856
|
+
}
|
|
2857
|
+
)._attachedEditor = {
|
|
2858
|
+
commands: { focus: focusSpy },
|
|
2859
|
+
destroy: vi.fn(),
|
|
2860
|
+
getJSON: () => ({
|
|
2861
|
+
type: "doc",
|
|
2862
|
+
content: [{ type: "paragraph" }],
|
|
2863
|
+
}),
|
|
2864
|
+
};
|
|
2865
|
+
await el.updateComplete;
|
|
2866
|
+
|
|
2867
|
+
const container = requireElement(
|
|
2868
|
+
el.querySelector<HTMLElement>(".compose-attached-tiptap"),
|
|
2869
|
+
"expected attached editor container",
|
|
2870
|
+
);
|
|
2871
|
+
container.dispatchEvent(
|
|
2872
|
+
new MouseEvent("mousedown", {
|
|
2873
|
+
bubbles: true,
|
|
2874
|
+
}),
|
|
2875
|
+
);
|
|
2876
|
+
|
|
2877
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
730
2878
|
});
|
|
731
2879
|
|
|
732
2880
|
it("confirm save draft dispatches submit-deferred with draft status", async () => {
|
|
@@ -1130,6 +3278,7 @@ describe("JantComposeDialog", () => {
|
|
|
1130
3278
|
"expected compose editor",
|
|
1131
3279
|
);
|
|
1132
3280
|
editor._rating = 3;
|
|
3281
|
+
editor._showRating = true;
|
|
1133
3282
|
await editor.updateComplete;
|
|
1134
3283
|
|
|
1135
3284
|
el.requestClose();
|
|
@@ -1137,4 +3286,20 @@ describe("JantComposeDialog", () => {
|
|
|
1137
3286
|
|
|
1138
3287
|
expect(el._confirmPanelOpen).toBe(true);
|
|
1139
3288
|
});
|
|
3289
|
+
|
|
3290
|
+
it("hidden rating does not trigger confirmation on close", async () => {
|
|
3291
|
+
const el = await createElement();
|
|
3292
|
+
const editor = requireElement(
|
|
3293
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
3294
|
+
"expected compose editor",
|
|
3295
|
+
);
|
|
3296
|
+
editor._rating = 3;
|
|
3297
|
+
editor._showRating = false;
|
|
3298
|
+
await editor.updateComplete;
|
|
3299
|
+
|
|
3300
|
+
el.requestClose();
|
|
3301
|
+
await el.updateComplete;
|
|
3302
|
+
|
|
3303
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
3304
|
+
});
|
|
1140
3305
|
});
|