@nuasite/collections-admin 0.43.0-beta.1
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/dist/types/app.d.ts +24 -0
- package/dist/types/app.d.ts.map +1 -0
- package/dist/types/app.js +240 -0
- package/dist/types/client.d.ts +75 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +134 -0
- package/dist/types/field-view.d.ts +17 -0
- package/dist/types/field-view.d.ts.map +1 -0
- package/dist/types/field-view.js +77 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +52 -0
- package/src/app.tsx +329 -0
- package/src/client.ts +216 -0
- package/src/css.d.ts +9 -0
- package/src/field-view.tsx +88 -0
- package/src/index.ts +24 -0
- package/src/styles.css +371 -0
- package/src/tsconfig.json +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":["../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../../node_modules/typescript/lib/lib.es2021.d.ts","../../../../node_modules/typescript/lib/lib.es2022.d.ts","../../../../node_modules/typescript/lib/lib.es2023.d.ts","../../../../node_modules/typescript/lib/lib.es2024.d.ts","../../../../node_modules/typescript/lib/lib.es2025.d.ts","../../../../node_modules/typescript/lib/lib.esnext.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../../../node_modules/typescript/lib/lib.es2025.collection.d.ts","../../../../node_modules/typescript/lib/lib.es2025.float16.d.ts","../../../../node_modules/typescript/lib/lib.es2025.intl.d.ts","../../../../node_modules/typescript/lib/lib.es2025.iterator.d.ts","../../../../node_modules/typescript/lib/lib.es2025.promise.d.ts","../../../../node_modules/typescript/lib/lib.es2025.regexp.d.ts","../../../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../../../node_modules/typescript/lib/lib.esnext.date.d.ts","../../../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../../../node_modules/typescript/lib/lib.esnext.error.d.ts","../../../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../../node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","../../../../node_modules/typescript/lib/lib.esnext.temporal.d.ts","../../../../node_modules/typescript/lib/lib.esnext.typedarrays.d.ts","../../../../node_modules/typescript/lib/lib.decorators.d.ts","../../../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../../node_modules/@types/react/global.d.ts","../../../../node_modules/csstype/index.d.ts","../../../../node_modules/@types/react/index.d.ts","../../../../node_modules/@types/react/jsx-runtime.d.ts","../../../cms-types/dist/types/index.d.ts","../../src/client.ts","../../src/field-view.tsx","../../src/app.tsx","../../src/css.d.ts","../../src/index.ts"],"fileIdsList":[[90,91],[92],[92,93,94,95,96,98],[93,94],[93,94,95,97]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"1e9332c23e9a907175e0ffc6a49e236f97b48838cc8aec9ce7e4cec21e544b65","impliedFormat":1},{"version":"3753fbc1113dc511214802a2342280a8b284ab9094f6420e7aa171e868679f91","impliedFormat":1},{"version":"999ca32883495a866aa5737fe1babc764a469e4cde6ee6b136a4b9ae68853e4b","impliedFormat":1},{"version":"17f13ecb98cbc39243f2eee1f16d45cd8ec4706b03ee314f1915f1a8b42f6984","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd714129fca875f7d4c477a1a392200b0bcd13fb2e80928cd334b63830ea047","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2c9037ae6cd2c52d80ceef0b3c5ffdb488627d71529cf4f63776daf11161c9a","affectsGlobalScope":true,"impliedFormat":1},{"version":"135d5cf4d345f59f1a9caadfafcd858d3d9cc68290db616cc85797224448cccc","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc238c3f81c2984751932b6aab223cd5b830e0ac6cad76389e5e9d2ffc03287d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a07f9b76d361f572620927e5735b77d6d2101c23cdd94383eb5b706e7b36357","affectsGlobalScope":true,"impliedFormat":1},{"version":"7c4e8dc6ab834cc6baa0227e030606d29e3e8449a9f67cdf5605ea5493c4db29","affectsGlobalScope":true,"impliedFormat":1},{"version":"de7ba0fd02e06cd9a5bd4ab441ed0e122735786e67dde1e849cced1cd8b46b78","affectsGlobalScope":true,"impliedFormat":1},{"version":"6148e4e88d720a06855071c3db02069434142a8332cf9c182cda551adedf3156","affectsGlobalScope":true,"impliedFormat":1},{"version":"d63dba625b108316a40c95a4425f8d4294e0deeccfd6c7e59d819efa19e23409","affectsGlobalScope":true,"impliedFormat":1},{"version":"0568d6befee03dd435bed4fc25c4e46865b24bdcb8c563fdc21f580a2c301904","affectsGlobalScope":true,"impliedFormat":1},{"version":"30d62269b05b584741f19a5369852d5d34895aa2ac4fd948956f886d15f9cc0d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"ffbe6d7b295306b2ba88030f65b74c107d8d99bdcf596ea99c62a02f606108b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"996fb27b15277369c68a4ba46ed138b4e9e839a02fb4ec756f7997629242fd9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"79b712591b270d4778c89706ca2cfc56ddb8c3f895840e477388f1710dc5eda9","affectsGlobalScope":true,"impliedFormat":1},{"version":"20884846cef428b992b9bd032e70a4ef88e349263f63aeddf04dda837a7dba26","affectsGlobalScope":true,"impliedFormat":1},{"version":"5fcab789c73a97cd43828ee3cc94a61264cf24d4c44472ce64ced0e0f148bdb2","affectsGlobalScope":true,"impliedFormat":1},{"version":"db59a81f070c1880ad645b2c0275022baa6a0c4f0acdc58d29d349c6efcf0903","affectsGlobalScope":true,"impliedFormat":1},{"version":"673294292640f5722b700e7d814e17aaf7d93f83a48a2c9b38f33cbc940ad8b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"d786b48f934cbca483b3c6d0a798cb43bbb4ada283e76fb22c28e53ae05b9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"142efd4ce210576f777dc34df121777be89eda476942d6d6663b03dcb53be3ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"379bc41580c2d774f82e828c70308f24a005b490c25ba34d679d84bcf05c3d9d","affectsGlobalScope":true,"impliedFormat":1},{"version":"ed484fb2aa8a1a23d0277056ec3336e0a0b52f9b8d6a961f338a642faf43235d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ffedae1d1c2d53fdbca1c96d3c7dda544281f7d262f99b6880634f8fd8d9820","affectsGlobalScope":true,"impliedFormat":1},{"version":"83a730b125d477dd264df8ba479afab27a3dae7152b005c214ab94dc7ee44fd3","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"2577e7e800bdece2956ca027cb5c17aa359abd968eb9355760110c853f4fb9da","impliedFormat":1},{"version":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","impliedFormat":1},"c43c2a0107a2a099fe50bcc754e90b50aa6b265ce57e464a2489798b38e4dc53",{"version":"cae82386b5fba54f17a7396bbba0de750fd09b5b7d0f08039cb7538d2b9886e2","signature":"272f4c7faec64979122122d98606088555fe082d32160376b1b9dcc40caf4efe"},{"version":"3e8cc18b22f303501b4924d79d2730d1ebcf3365d993ab0ca19994c2cc6337bc","signature":"1bda9166693fe332fa4550821e176105daef955391b38071985daa47055a7011"},{"version":"1d9991b027426d9bd5513e0ac4521867bbcacb537bfe49d24dae939ba330c9b7","signature":"24d4a8d64adadf8ab262d66229e5485239556e6221d52bd05c893a7b86472aa5"},"35d32b2f157a4e27bfae13e57e73d458fb71335af717f590532c77e2b323d530",{"version":"670019c1f499438c6d5f2398deae4286ecd68c75ab10e9d921d3cf210a9efb40","signature":"1fa1ce30428a7b953b96fe89509a93927f7c047c7ebf0db68f0d143e5d880af6"}],"root":[[95,99]],"options":{"allowImportingTsExtensions":true,"allowJs":true,"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":true,"jsx":4,"module":200,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":false,"noUncheckedIndexedAccess":true,"noUnusedLocals":false,"noUnusedParameters":false,"outDir":"./","skipLibCheck":true,"strict":true,"target":99,"verbatimModuleSyntax":true},"referencedMap":[[92,1],[93,2],[97,3],[95,4],[96,4],[99,5]],"latestChangedDtsFile":"./index.d.ts","version":"6.0.2"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nuasite/collections-admin",
|
|
3
|
+
"description": "Host-agnostic React SPA for browsing and editing Nua CMS collections over the cms-sidecar HTTP contract.",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist/**",
|
|
6
|
+
"src/**",
|
|
7
|
+
"README.md",
|
|
8
|
+
"package.json"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/nuasite/nuasite/blob/main/packages/collections-admin/README.md",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/nuasite/nuasite.git",
|
|
14
|
+
"directory": "packages/collections-admin"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"version": "0.43.0-beta.1",
|
|
18
|
+
"module": "src/index.ts",
|
|
19
|
+
"types": "src/index.ts",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./src/index.ts",
|
|
24
|
+
"import": "./src/index.ts",
|
|
25
|
+
"default": "./src/index.ts"
|
|
26
|
+
},
|
|
27
|
+
"./styles.css": "./src/styles.css"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@nuasite/cms-types": "0.43.0-beta.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^19.2.7",
|
|
34
|
+
"@types/react-dom": "^19.2.3",
|
|
35
|
+
"react": "^19.2.1",
|
|
36
|
+
"react-dom": "^19.2.1"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^19.0.0",
|
|
40
|
+
"react-dom": "^19.0.0",
|
|
41
|
+
"typescript": "^6.0.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"prepack": "bun run ../../scripts/workspace-deps/resolve-deps.ts"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"cms",
|
|
48
|
+
"nuasite",
|
|
49
|
+
"react",
|
|
50
|
+
"admin"
|
|
51
|
+
]
|
|
52
|
+
}
|
package/src/app.tsx
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collections-admin SPA — read-only milestone (cms-headless F3.1).
|
|
3
|
+
*
|
|
4
|
+
* Host-agnostic: driven only by an `apiBase` prop, with internal view-state
|
|
5
|
+
* navigation (list → entries → detail) via React state — never the host router.
|
|
6
|
+
* That keeps the same component usable as a webmaster tab today and at
|
|
7
|
+
* `/_nua/admin` for local dev later (F7).
|
|
8
|
+
*
|
|
9
|
+
* Read-only: browse collections, list entries (sparse projection + cursor
|
|
10
|
+
* pagination), and view a single entry's frontmatter + markdown body. Mutations
|
|
11
|
+
* (editor/media/conflict) arrive in F3.2.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CollectionDefinition, CollectionEntry, CollectionEntryInfo, FieldDefinition } from '@nuasite/cms-types'
|
|
15
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
16
|
+
import { type CmsClient, CmsClientError, createClient } from './client'
|
|
17
|
+
import { FieldRow } from './field-view'
|
|
18
|
+
import './styles.css'
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// View state
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
type View =
|
|
25
|
+
| { view: 'list' }
|
|
26
|
+
| { view: 'entries'; collection: string }
|
|
27
|
+
| { view: 'detail'; collection: string; slug: string }
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Shared async-load hook
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
interface LoadState<T> {
|
|
34
|
+
data: T | null
|
|
35
|
+
error: CmsClientError | Error | null
|
|
36
|
+
loading: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function useAsync<T>(load: () => Promise<T>, deps: readonly unknown[]): LoadState<T> & { reload: () => void } {
|
|
40
|
+
const [state, setState] = useState<LoadState<T>>({ data: null, error: null, loading: true })
|
|
41
|
+
const [nonce, setNonce] = useState(0)
|
|
42
|
+
const loadRef = useRef(load)
|
|
43
|
+
loadRef.current = load
|
|
44
|
+
|
|
45
|
+
// `load` is read through a ref; re-runs are driven by the explicit `deps` and
|
|
46
|
+
// the reload `nonce` so the effect deps stay stable and lint-clean.
|
|
47
|
+
const effectDeps = [...deps, nonce]
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
let active = true
|
|
50
|
+
setState({ data: null, error: null, loading: true })
|
|
51
|
+
loadRef.current().then(
|
|
52
|
+
(data) => {
|
|
53
|
+
if (active) setState({ data, error: null, loading: false })
|
|
54
|
+
},
|
|
55
|
+
(error: unknown) => {
|
|
56
|
+
if (active) setState({ data: null, error: error instanceof Error ? error : new Error(String(error)), loading: false })
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
return () => {
|
|
60
|
+
active = false
|
|
61
|
+
}
|
|
62
|
+
}, effectDeps)
|
|
63
|
+
|
|
64
|
+
const reload = useCallback(() => setNonce((n) => n + 1), [])
|
|
65
|
+
return { ...state, reload }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Presentational primitives
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
function Spinner({ label }: { label: string }) {
|
|
73
|
+
return (
|
|
74
|
+
<div className="nua-cadmin-state">
|
|
75
|
+
<div className="nua-cadmin-spinner" />
|
|
76
|
+
<div>{label}</div>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ErrorState({ error, onRetry }: { error: CmsClientError | Error; onRetry?: () => void }) {
|
|
82
|
+
const title = error instanceof CmsClientError && error.isUnauthorized
|
|
83
|
+
? 'Session expired'
|
|
84
|
+
: error instanceof CmsClientError && error.isForbidden
|
|
85
|
+
? 'No access'
|
|
86
|
+
: 'Something went wrong'
|
|
87
|
+
return (
|
|
88
|
+
<div className="nua-cadmin-error">
|
|
89
|
+
<div className="nua-cadmin-error-title">{title}</div>
|
|
90
|
+
<div>{error.message}</div>
|
|
91
|
+
{onRetry ? <button type="button" className="nua-cadmin-retry" onClick={onRetry}>Try again</button> : null}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function EmptyState({ label }: { label: string }) {
|
|
97
|
+
return <div className="nua-cadmin-state">{label}</div>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Collection list
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
function CollectionList({ client, onOpen }: { client: CmsClient; onOpen: (collection: string) => void }) {
|
|
105
|
+
const { data, error, loading, reload } = useAsync(() => client.getCollections(), [client])
|
|
106
|
+
|
|
107
|
+
if (loading) return <Spinner label="Loading collections…" />
|
|
108
|
+
if (error) return <ErrorState error={error} onRetry={reload} />
|
|
109
|
+
if (!data || data.length === 0) return <EmptyState label="No collections found in this project." />
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="nua-cadmin-list">
|
|
113
|
+
{data.map((collection) => (
|
|
114
|
+
<button key={collection.name} type="button" className="nua-cadmin-card" onClick={() => onOpen(collection.name)}>
|
|
115
|
+
<span className="nua-cadmin-card-main">
|
|
116
|
+
<span className="nua-cadmin-card-label">{collection.label || collection.name}</span>
|
|
117
|
+
<span className="nua-cadmin-card-sub">
|
|
118
|
+
{collection.name}
|
|
119
|
+
{collection.type ? ` · ${collection.type}` : ''}
|
|
120
|
+
{` · ${collection.fileExtension}`}
|
|
121
|
+
</span>
|
|
122
|
+
</span>
|
|
123
|
+
<span className="nua-cadmin-badge">{collection.entryCount} {collection.entryCount === 1 ? 'entry' : 'entries'}</span>
|
|
124
|
+
</button>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Entries table (sparse projection + cursor pagination)
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
const ENTRIES_PAGE_SIZE = 50
|
|
135
|
+
const ENTRIES_FIELDS = 'slug,title,draft,pathname'
|
|
136
|
+
|
|
137
|
+
function EntriesTable({ client, collection, onOpen }: { client: CmsClient; collection: string; onOpen: (slug: string) => void }) {
|
|
138
|
+
const [rows, setRows] = useState<CollectionEntryInfo[]>([])
|
|
139
|
+
const [cursor, setCursor] = useState<string | undefined>(undefined)
|
|
140
|
+
const [hasMore, setHasMore] = useState(false)
|
|
141
|
+
const [error, setError] = useState<CmsClientError | Error | null>(null)
|
|
142
|
+
const [loading, setLoading] = useState(true)
|
|
143
|
+
const [loadingMore, setLoadingMore] = useState(false)
|
|
144
|
+
|
|
145
|
+
const loadPage = useCallback(
|
|
146
|
+
async (nextCursor: string | undefined, append: boolean) => {
|
|
147
|
+
if (append) setLoadingMore(true)
|
|
148
|
+
else setLoading(true)
|
|
149
|
+
setError(null)
|
|
150
|
+
try {
|
|
151
|
+
const result = await client.getEntries(collection, {
|
|
152
|
+
fields: ENTRIES_FIELDS,
|
|
153
|
+
draft: 'all',
|
|
154
|
+
limit: ENTRIES_PAGE_SIZE,
|
|
155
|
+
cursor: nextCursor,
|
|
156
|
+
})
|
|
157
|
+
setRows((prev) => (append ? [...prev, ...result.entries] : result.entries))
|
|
158
|
+
setCursor(result.cursor)
|
|
159
|
+
setHasMore(result.hasMore)
|
|
160
|
+
} catch (e: unknown) {
|
|
161
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
162
|
+
} finally {
|
|
163
|
+
setLoading(false)
|
|
164
|
+
setLoadingMore(false)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[client, collection],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
setRows([])
|
|
172
|
+
setCursor(undefined)
|
|
173
|
+
setHasMore(false)
|
|
174
|
+
void loadPage(undefined, false)
|
|
175
|
+
}, [loadPage])
|
|
176
|
+
|
|
177
|
+
if (loading) return <Spinner label="Loading entries…" />
|
|
178
|
+
if (error) return <ErrorState error={error} onRetry={() => void loadPage(undefined, false)} />
|
|
179
|
+
if (rows.length === 0) return <EmptyState label="This collection has no entries." />
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
<table className="nua-cadmin-table">
|
|
184
|
+
<thead>
|
|
185
|
+
<tr>
|
|
186
|
+
<th>Slug</th>
|
|
187
|
+
<th>Title</th>
|
|
188
|
+
<th>Draft</th>
|
|
189
|
+
<th>Pathname</th>
|
|
190
|
+
</tr>
|
|
191
|
+
</thead>
|
|
192
|
+
<tbody>
|
|
193
|
+
{rows.map((entry) => (
|
|
194
|
+
<tr key={entry.slug} className="nua-cadmin-row" onClick={() => onOpen(entry.slug)}>
|
|
195
|
+
<td className="nua-cadmin-cell-mono">{entry.slug}</td>
|
|
196
|
+
<td>{entry.title ?? <span className="nua-cadmin-field-empty">—</span>}</td>
|
|
197
|
+
<td>{entry.draft ? <span className="nua-cadmin-badge nua-cadmin-badge-draft">draft</span> : ''}</td>
|
|
198
|
+
<td className="nua-cadmin-cell-mono">{entry.pathname ?? '—'}</td>
|
|
199
|
+
</tr>
|
|
200
|
+
))}
|
|
201
|
+
</tbody>
|
|
202
|
+
</table>
|
|
203
|
+
{hasMore
|
|
204
|
+
? (
|
|
205
|
+
<button type="button" className="nua-cadmin-load-more" disabled={loadingMore} onClick={() => void loadPage(cursor, true)}>
|
|
206
|
+
{loadingMore ? 'Loading…' : 'Load more'}
|
|
207
|
+
</button>
|
|
208
|
+
)
|
|
209
|
+
: null}
|
|
210
|
+
</div>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Entry detail
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Order the collection's fields for display: `publish-toggle`/`publish-date`
|
|
220
|
+
* roles and `sidebar`/`header` positioned fields first, then the rest in schema
|
|
221
|
+
* order. Hidden fields are dropped.
|
|
222
|
+
*/
|
|
223
|
+
function orderFields(fields: FieldDefinition[]): FieldDefinition[] {
|
|
224
|
+
const visible = fields.filter((f) => !f.hidden)
|
|
225
|
+
const pinned = visible.filter((f) => f.role !== undefined || f.position !== undefined)
|
|
226
|
+
const rest = visible.filter((f) => f.role === undefined && f.position === undefined)
|
|
227
|
+
return [...pinned, ...rest]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function EntryDetail({ client, collections, collection, slug }: {
|
|
231
|
+
client: CmsClient
|
|
232
|
+
collections: CollectionDefinition[]
|
|
233
|
+
collection: string
|
|
234
|
+
slug: string
|
|
235
|
+
}) {
|
|
236
|
+
const { data, error, loading, reload } = useAsync<CollectionEntry>(() => client.getEntry(collection, slug), [client, collection, slug])
|
|
237
|
+
|
|
238
|
+
const def = useMemo(() => collections.find((c) => c.name === collection), [collections, collection])
|
|
239
|
+
|
|
240
|
+
if (loading) return <Spinner label="Loading entry…" />
|
|
241
|
+
if (error) return <ErrorState error={error} onRetry={reload} />
|
|
242
|
+
if (!data) return <EmptyState label="Entry not found." />
|
|
243
|
+
|
|
244
|
+
const fieldDefs = def ? orderFields(def.fields) : []
|
|
245
|
+
const renderedNames = new Set(fieldDefs.map((f) => f.name))
|
|
246
|
+
// Frontmatter keys present on the entry but absent from the inferred schema.
|
|
247
|
+
const extraKeys = Object.keys(data.frontmatter).filter((k) => !renderedNames.has(k))
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div>
|
|
251
|
+
<div className="nua-cadmin-fields">
|
|
252
|
+
{fieldDefs.length === 0 && extraKeys.length === 0 ? <EmptyState label="No frontmatter fields." /> : null}
|
|
253
|
+
{fieldDefs.map((field) => <FieldRow key={field.name} field={field} raw={data.frontmatter[field.name]?.value} />)}
|
|
254
|
+
{extraKeys.map((key) => <FieldRow key={key} field={{ name: key, type: 'text', required: false }} raw={data.frontmatter[key]?.value} />)}
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<h3 className="nua-cadmin-section-title">Body</h3>
|
|
258
|
+
{data.body.trim() === ''
|
|
259
|
+
? <EmptyState label="This entry has no markdown body." />
|
|
260
|
+
: <pre className="nua-cadmin-body-content">{data.body}</pre>}
|
|
261
|
+
</div>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============================================================================
|
|
266
|
+
// Root
|
|
267
|
+
// ============================================================================
|
|
268
|
+
|
|
269
|
+
export interface CollectionsAdminAppProps {
|
|
270
|
+
/**
|
|
271
|
+
* Base URL the cms-sidecar is mounted under (the host adds the `/cms/v1`
|
|
272
|
+
* prefix). In webmaster this is `/app/project/:slug/session/:sessionId/cms`.
|
|
273
|
+
*/
|
|
274
|
+
apiBase: string
|
|
275
|
+
/** Optional close affordance shown in the header (e.g. to collapse the WM tab). */
|
|
276
|
+
onClose?: () => void
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function CollectionsAdminApp({ apiBase, onClose }: CollectionsAdminAppProps) {
|
|
280
|
+
const client = useMemo(() => createClient(apiBase), [apiBase])
|
|
281
|
+
const [state, setState] = useState<View>({ view: 'list' })
|
|
282
|
+
|
|
283
|
+
// The collection definitions are needed by the detail view to drive field
|
|
284
|
+
// rendering; load them once at the root and pass down.
|
|
285
|
+
const collectionsState = useAsync(() => client.getCollections(), [client])
|
|
286
|
+
const collections = collectionsState.data ?? []
|
|
287
|
+
|
|
288
|
+
const goList = useCallback(() => setState({ view: 'list' }), [])
|
|
289
|
+
const goEntries = useCallback((collection: string) => setState({ view: 'entries', collection }), [])
|
|
290
|
+
const goDetail = useCallback((collection: string, slug: string) => setState({ view: 'detail', collection, slug }), [])
|
|
291
|
+
|
|
292
|
+
const activeCollection = state.view !== 'list'
|
|
293
|
+
? collections.find((c) => c.name === state.collection)
|
|
294
|
+
: undefined
|
|
295
|
+
const collectionLabel = activeCollection ? (activeCollection.label || activeCollection.name) : (state.view !== 'list' ? state.collection : '')
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<div className="nua-cadmin">
|
|
299
|
+
<div className="nua-cadmin-header">
|
|
300
|
+
{state.view === 'entries' ? <button type="button" className="nua-cadmin-back" onClick={goList}>← Collections</button> : null}
|
|
301
|
+
{state.view === 'detail'
|
|
302
|
+
? <button type="button" className="nua-cadmin-back" onClick={() => goEntries(state.collection)}>← {collectionLabel}</button>
|
|
303
|
+
: null}
|
|
304
|
+
|
|
305
|
+
{state.view === 'list' ? <h2 className="nua-cadmin-title">Collections</h2> : null}
|
|
306
|
+
{state.view === 'entries' ? <h2 className="nua-cadmin-title">{collectionLabel}</h2> : null}
|
|
307
|
+
{state.view === 'detail'
|
|
308
|
+
? (
|
|
309
|
+
<h2 className="nua-cadmin-title">
|
|
310
|
+
{collectionLabel}
|
|
311
|
+
<span className="nua-cadmin-crumb">/ {state.slug}</span>
|
|
312
|
+
</h2>
|
|
313
|
+
)
|
|
314
|
+
: null}
|
|
315
|
+
|
|
316
|
+
<span className="nua-cadmin-spacer" />
|
|
317
|
+
{onClose ? <button type="button" className="nua-cadmin-close" aria-label="Close" onClick={onClose}>×</button> : null}
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div className="nua-cadmin-body">
|
|
321
|
+
{state.view === 'list' ? <CollectionList client={client} onOpen={goEntries} /> : null}
|
|
322
|
+
{state.view === 'entries'
|
|
323
|
+
? <EntriesTable client={client} collection={state.collection} onOpen={(slug) => goDetail(state.collection, slug)} />
|
|
324
|
+
: null}
|
|
325
|
+
{state.view === 'detail' ? <EntryDetail client={client} collections={collections} collection={state.collection} slug={state.slug} /> : null}
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
)
|
|
329
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed read-only client over the cms-sidecar `/cms/v1` HTTP contract.
|
|
3
|
+
*
|
|
4
|
+
* The host (webmaster BFF, or a local dev proxy in F7) mounts the sidecar under
|
|
5
|
+
* an `apiBase` and adds the `/cms/v1` prefix itself — so this client requests
|
|
6
|
+
* `${apiBase}/project`, `${apiBase}/collections`, etc. (never `/cms/v1/...`).
|
|
7
|
+
*
|
|
8
|
+
* The structural model (collections/entries/fields) is reused 1:1 from
|
|
9
|
+
* `@nuasite/cms-types`. The thin HTTP envelope (project model, sparse entries
|
|
10
|
+
* list, error codes) mirrors the sidecar's wire types; it is declared here
|
|
11
|
+
* because those types are not part of the `@nuasite/cms-types` contract surface.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CollectionDefinition, CollectionEntry, CollectionEntryInfo } from '@nuasite/cms-types'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Wire envelope (mirrors @nuasite/cms-sidecar's `/cms/v1` contract)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/** Stable error codes the sidecar exposes, each mapped to an HTTP status. */
|
|
21
|
+
export type CmsErrorCode =
|
|
22
|
+
| 'not_found'
|
|
23
|
+
| 'conflict'
|
|
24
|
+
| 'validation'
|
|
25
|
+
| 'parse_error'
|
|
26
|
+
| 'io_error'
|
|
27
|
+
| 'unsupported'
|
|
28
|
+
| 'unauthorized'
|
|
29
|
+
|
|
30
|
+
/** JSON body returned for every non-2xx response that is not a conflict. */
|
|
31
|
+
export interface CmsApiError {
|
|
32
|
+
error: string
|
|
33
|
+
code: CmsErrorCode
|
|
34
|
+
sourcePath?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A static page route discovered under `src/pages` (pathname-only). */
|
|
38
|
+
export interface CmsPageEntry {
|
|
39
|
+
pathname: string
|
|
40
|
+
title?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Features the sidecar advertises so the UI can degrade gracefully. */
|
|
44
|
+
export interface CmsCapabilities {
|
|
45
|
+
coreVersion: string
|
|
46
|
+
features: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `GET /project` — the whole structural model in one call. */
|
|
50
|
+
export interface CmsProjectModel {
|
|
51
|
+
collections: CollectionDefinition[]
|
|
52
|
+
pages: CmsPageEntry[]
|
|
53
|
+
capabilities: CmsCapabilities
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `GET …/entries` — projected entries plus an opaque continuation cursor. */
|
|
57
|
+
export interface CmsEntriesListResult {
|
|
58
|
+
entries: CollectionEntryInfo[]
|
|
59
|
+
cursor?: string
|
|
60
|
+
hasMore: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Client error
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Thrown for any non-2xx response. Carries the parsed sidecar error code so the
|
|
69
|
+
* UI can distinguish auth failures (`unauthorized`/`forbidden`) from a missing
|
|
70
|
+
* collection/entry (`not_found`) or a generic failure.
|
|
71
|
+
*/
|
|
72
|
+
export class CmsClientError extends Error {
|
|
73
|
+
constructor(
|
|
74
|
+
readonly status: number,
|
|
75
|
+
readonly code: CmsErrorCode | 'forbidden' | 'unknown',
|
|
76
|
+
message: string,
|
|
77
|
+
) {
|
|
78
|
+
super(message)
|
|
79
|
+
this.name = 'CmsClientError'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Session cookie missing/expired upstream — the user must re-authenticate. */
|
|
83
|
+
get isUnauthorized(): boolean {
|
|
84
|
+
return this.code === 'unauthorized' || this.status === 401
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Authenticated but lacks access to this project. */
|
|
88
|
+
get isForbidden(): boolean {
|
|
89
|
+
return this.code === 'forbidden' || this.status === 403
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get isNotFound(): boolean {
|
|
93
|
+
return this.code === 'not_found' || this.status === 404
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Query options
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
export interface GetEntriesOptions {
|
|
102
|
+
/** "slug,title" | "*" ; absent = light header (slug/title/draft/pathname). */
|
|
103
|
+
fields?: string
|
|
104
|
+
/** Draft filter — defaults to `'false'` (published only) on the sidecar. */
|
|
105
|
+
draft?: 'true' | 'false' | 'all'
|
|
106
|
+
/** Opaque continuation cursor from a previous page's `cursor`. */
|
|
107
|
+
cursor?: string
|
|
108
|
+
limit?: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Client
|
|
113
|
+
// ============================================================================
|
|
114
|
+
|
|
115
|
+
function isApiError(value: unknown): value is CmsApiError {
|
|
116
|
+
return typeof value === 'object'
|
|
117
|
+
&& value !== null
|
|
118
|
+
&& 'error' in value
|
|
119
|
+
&& typeof (value as { error: unknown }).error === 'string'
|
|
120
|
+
&& 'code' in value
|
|
121
|
+
&& typeof (value as { code: unknown }).code === 'string'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const KNOWN_ERROR_CODES: readonly CmsErrorCode[] = [
|
|
125
|
+
'not_found',
|
|
126
|
+
'conflict',
|
|
127
|
+
'validation',
|
|
128
|
+
'parse_error',
|
|
129
|
+
'io_error',
|
|
130
|
+
'unsupported',
|
|
131
|
+
'unauthorized',
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
function isErrorCode(value: string): value is CmsErrorCode {
|
|
135
|
+
return (KNOWN_ERROR_CODES as readonly string[]).includes(value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface CmsClient {
|
|
139
|
+
getProject(): Promise<CmsProjectModel>
|
|
140
|
+
getCollections(): Promise<CollectionDefinition[]>
|
|
141
|
+
getEntries(collection: string, options?: GetEntriesOptions): Promise<CmsEntriesListResult>
|
|
142
|
+
getEntry(collection: string, slug: string): Promise<CollectionEntry>
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function createClient(apiBase: string): CmsClient {
|
|
146
|
+
// Normalise: drop a trailing slash so `${base}${path}` joins cleanly.
|
|
147
|
+
const base = apiBase.endsWith('/') ? apiBase.slice(0, -1) : apiBase
|
|
148
|
+
|
|
149
|
+
async function request<T>(path: string): Promise<T> {
|
|
150
|
+
const response = await fetch(`${base}${path}`, {
|
|
151
|
+
method: 'GET',
|
|
152
|
+
credentials: 'include',
|
|
153
|
+
headers: { accept: 'application/json' },
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw await toError(response)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Successful responses are always JSON in the read-only surface.
|
|
161
|
+
const value: T = await response.json()
|
|
162
|
+
return value
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function toError(response: Response): Promise<CmsClientError> {
|
|
166
|
+
// 403 is produced by the BFF (project scope), not the sidecar, so it has no
|
|
167
|
+
// sidecar `code`; surface it as a distinct `forbidden`.
|
|
168
|
+
if (response.status === 403) {
|
|
169
|
+
const message = await readErrorMessage(response, 'You do not have access to this project.')
|
|
170
|
+
return new CmsClientError(403, 'forbidden', message)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const body: unknown = await response.json().catch(() => null)
|
|
174
|
+
if (isApiError(body) && isErrorCode(body.code)) {
|
|
175
|
+
return new CmsClientError(response.status, body.code, body.error)
|
|
176
|
+
}
|
|
177
|
+
if (response.status === 401) {
|
|
178
|
+
return new CmsClientError(401, 'unauthorized', 'Your session has expired. Please reload.')
|
|
179
|
+
}
|
|
180
|
+
return new CmsClientError(response.status, 'unknown', `Request failed (${response.status})`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function readErrorMessage(response: Response, fallback: string): Promise<string> {
|
|
184
|
+
const body: unknown = await response.json().catch(() => null)
|
|
185
|
+
if (isApiError(body)) return body.error
|
|
186
|
+
if (typeof body === 'object' && body !== null && 'error' in body) {
|
|
187
|
+
const err = (body as { error: unknown }).error
|
|
188
|
+
if (typeof err === 'object' && err !== null && 'message' in err && typeof (err as { message: unknown }).message === 'string') {
|
|
189
|
+
return (err as { message: string }).message
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return fallback
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
getProject() {
|
|
197
|
+
return request<CmsProjectModel>('/project')
|
|
198
|
+
},
|
|
199
|
+
getCollections() {
|
|
200
|
+
return request<CollectionDefinition[]>('/collections')
|
|
201
|
+
},
|
|
202
|
+
getEntries(collection, options = {}) {
|
|
203
|
+
const params = new URLSearchParams()
|
|
204
|
+
if (options.fields !== undefined) params.set('fields', options.fields)
|
|
205
|
+
if (options.draft !== undefined) params.set('draft', options.draft)
|
|
206
|
+
if (options.cursor !== undefined) params.set('cursor', options.cursor)
|
|
207
|
+
if (options.limit !== undefined) params.set('limit', String(options.limit))
|
|
208
|
+
const query = params.toString()
|
|
209
|
+
const suffix = query === '' ? '' : `?${query}`
|
|
210
|
+
return request<CmsEntriesListResult>(`/collections/${encodeURIComponent(collection)}/entries${suffix}`)
|
|
211
|
+
},
|
|
212
|
+
getEntry(collection, slug) {
|
|
213
|
+
return request<CollectionEntry>(`/collections/${encodeURIComponent(collection)}/entries/${encodeURIComponent(slug)}`)
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
}
|
package/src/css.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient declaration so a side-effect `import './styles.css'` typechecks in the
|
|
3
|
+
* lib's own build. Hosts (webmaster) already declare `*.css` via `vite/client`,
|
|
4
|
+
* and the bundler resolves the real file at build time.
|
|
5
|
+
*/
|
|
6
|
+
declare module '*.css' {
|
|
7
|
+
const css: string
|
|
8
|
+
export default css
|
|
9
|
+
}
|